George Sakkis wrote: > On Jun 11, 8:27 am, Frank Millman <[EMAIL PROTECTED]> wrote: >> On Jun 11, 1:56 pm, Steven D'Aprano >> >> <[EMAIL PROTECTED]> 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 -- http://mail.python.org/mailman/listinfo/python-list