Postpone creation of attributes until needed

Steven Bethard steven.bethard at gmail.com
Mon Jun 11 15:55:29 EDT 2007


George Sakkis wrote:
> On Jun 11, 8:27 am, Frank Millman <f... at chagford.com> wrote:
>> On Jun 11, 1:56 pm, Steven D'Aprano
>>
>> <s... at REMOVE.THIS.cybersource.com.au> wrote:
>>
>>> Unless you have thousands and thousands of instances, __slots__ is almost
>>> certainly not the answer. __slots__ is an optimization to minimize the
>>> size of each instance. The fact that it prevents the creation of new
>>> attributes is a side-effect.
>> Understood - I am getting there slowly.
>>
>> I now have the following -
>>
>>>>> class A(object):
>> ...    def __init__(self,x,y):
>> ...        self.x = x
>> ...        self.y = y
>> ...    def __getattr__(self,name):
>> ...        print 'getattr',name
>> ...        self.compute()
>> ...        return self.__dict__[name]
>> ...    def compute(self):  # compute all missing attributes
>> ...        self.__dict__['z'] = self.x * self.y
>>            [there could be many of these]
>>
>>>>> a = A(3,4)
>>>>> a.x
>> 3
>>>>> a.y
>> 4
>>>>> a.z
>> getattr z
>> 12>>> a.z
>> 12
>>>>> a.q
>> KeyError: 'q'
>>
>> The only problem with this is that it raises KeyError instead of the
>> expected AttributeError.
>>
>>
>>
>>> You haven't told us what the 'compute' method is.
>>> Or if you have, I missed it.
>> Sorry - I made it more explicit above. It is the method that sets up
>> all the missing attributes. No matter which attribute is referenced
>> first, 'compute' sets up all of them, so they are all available for
>> any future reference.
>>
>> To be honest, it feels neater than setting up a property for each
>> attribute.
> 
> I don't see why this all-or-nothing approach is neater; what if you
> have a hundred expensive computed attributes but you just need one ?
> Unless you know this never happens in your specific situation because
> all missing attributes are tightly coupled, properties are a better
> way to go. The boilerplate code can be minimal too with an appropriate
> decorator, something like:
> 
> class A(object):
> 
>     def __init__(self,x,y):
>         self.x = x
>         self.y = y
> 
>     @cachedproperty
>     def z(self):
>         return self.x * self.y
> 
> 
> where cachedproperty is
> 
> def cachedproperty(func):
>     name = '__' + func.__name__
>     def wrapper(self):
>         try: return getattr(self, name)
>         except AttributeError: # raised only the first time
>             value = func(self)
>             setattr(self, name, value)
>             return value
>     return property(wrapper)

And, if you don't want to go through the property machinery every time, 
you can use a descriptor that only calls the function the first time:

 >>> class Once(object):
...     def __init__(self, func):
...         self.func = func
...     def __get__(self, obj, cls=None):
...         if obj is None:
...             return self
...         else:
...             value = self.func(obj)
...             setattr(obj, self.func.__name__, value)
...             return value
...
 >>> class A(object):
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...     @Once
...     def z(self):
...         print 'calculating z'
...         return self.x * self.y
...
 >>> a = A(2, 3)
 >>> a.z
calculating z
6
 >>> a.z
6

With this approach, the first time 'z' is accessed, there is no 
instance-level 'z', so the descriptor's __get__ method is invoked. That 
method creates an instance-level 'z' so that every other time, the 
instance-level attribute is used (and the __get__ method is no longer 
invoked).

STeVe



More information about the Python-list mailing list