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...
--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to