Postpone creation of attributes until needed

George Sakkis george.sakkis at gmail.com
Mon Jun 11 09:38:08 EDT 2007


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)


HTH,

George




More information about the Python-list mailing list