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