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/pyt...ay/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...