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