Computing class variable on demand?

Steven Bethard steven.bethard at gmail.com
Sat Feb 5 18:47:23 EST 2005


fortepianissimo wrote:
> Thanks Steve - actually my question was simpler than that. I just
> wanted to use Daniels' recipe of lazy initialization on objects with
> __slots__:
> 
> class Lazy(object):
>     def __init__(self, calculate_function):
>         self._calculate = calculate_function
> 
>     def __get__(self, obj, _=None):
>         if obj is None:
>             return self
>         value = self._calculate(obj)
>         setattr(obj, self._calculate.func_name, value)
>         return value
> 
> 
> class SomeClass(object):
> 
>     __slots__ = 'someprop'
> 
>     @Lazy
>     def someprop(self):
>         print 'Actually calculating value'
>         return 13
> 
> 
> o = SomeClass()
> print o.someprop
> print o.someprop
> 
> 
> 
> Running the code above will produce:
> 
> Actually calculating value
> Traceback (most recent call last):
>   File "Lazy.py", line 26, in ?
>     print o.someprop
>   File "Lazy.py", line 11, in __get__
>     setattr(obj, self._calculate.func_name, value)
> AttributeError: 'SomeClass' object attribute 'someprop' is read-only
> 
> 
> Removing the __slots__ statement, everything would run normally.
> 
> Is there any workaround?


Hmm...  Well, there's a thread on a similar topic:

http://mail.python.org/pipermail/python-dev/2003-May/035575.html

The summary is basically that '__slots__' creates descriptors for all 
the names in the __slots__ iterable.  If you define a function (or a 
Lazy object) at the class level with the same name as a slot, it 
replaces the descriptor for that slot name.  So then when setattr gets 
called, it can't find the descriptor anymore, so it can't write the value...

One workaround would be to have Lazy store the values instead of setting 
the attribute on the instance -- something like:

py> class Lazy(object):
...     def __init__(self, calculate_function):
...         self._calculate = calculate_function
...         self._values = {}
...     def __get__(self, obj, _=None):
...         if obj is None:
...             return self
...         obj_id = id(obj)
...         if obj_id not in self._values:
...             self._values[obj_id] = self._calculate(obj)
...         return self._values[obj_id]
...
py>
py> class SomeClass(object):
...     __slots__ = []
...     @Lazy
...     def someprop(self):
...         print 'Actually calculating value'
...         return 13
...
py> o = SomeClass()
py> o.someprop
Actually calculating value
13
py> o.someprop
13

Basically Lazy just stores a mapping from object ids to calculated 
values.  Not as pretty as the original solution, but if you have to have 
__slots__ defined[1], I guess this might work.

Note that I don't need to declare any slots since the someprop 
descriptor is a class-level attribute, not an instance-level one...

Steve

[1] Are you sure you have to use __slots__?  Unless you're creating a 
very large number of these objects, you probably won't notice the 
difference in memory consumption...



More information about the Python-list mailing list