kj a écrit :
In <mailman.1030.1269194878.23598.python-l...@python.org> Dennis Lee Bieber 
<wlfr...@ix.netcom.com> writes:

On Sun, 21 Mar 2010 16:57:40 +0000 (UTC), kj <no.em...@please.post>
declaimed the following in gmane.comp.python.general:

Regarding properties, is there a built-in way to memoize them? For
example, suppose that the value of a property is obtained by parsing
the contents of a file (specified in another instance attribute).
It would make no sense to do this parsing more than once.  Is there
a standard idiom for memoizing the value once it is determined for
the first time?

        Pickle, Shelve? Maybe in conjunction with SQLite3...

I was thinking of something less persistent; in-memory, that is.
Maybe something in the spirit of:

@property
def foo(self):
    # up for some "adaptive auto-redefinition"?
    self.foo = self._some_time_consuming_operation()
    return self.foo

...except that that assignment won't work! It bombs with "AttributeError:
can't set attribute".

~K

PS: BTW, this is not the first time that attempting to set an
attribute (in a class written by me even) blows up on me.  It's
situations like these that rattle my grasp of attributes, hence my
original question about boring, plodding, verbose Java-oid accessors.
For me these Python attributes are still waaay too mysterious and
unpredictable to rely on.

Somehow simplified, here's what you have to know:

1/ there are instance attributes and class attributes. Instance attributes lives in the instance's __dict__, class attributes lives in the class's __dict__ or in a parent's class __dict__.

2/ when looking up an attribute on an instance, the rules are
* first, check if there's a key by that name in the instance's __dict__. If yes, return the associated value
* else, check if there's a class or parent class attribute by that name.
* if yes
** if the attribute has a '__get__' method, call the __get__ method with class and instance as arguments, and return the result (this is known as the "descriptor protocol" and provides support for computed attributes (including methods and properties)
** else return the attribute itself
* else (if nothing has been found yet), look for a __getattr__ method in the class and it's parents. If found, call this __getattr__ method with the attribute name and return the result
* else, give up and raise an AttributeError

3/ When binding an attribute on an instance, the rules are:
* first, check if there's a class (or parent class) attribute by that name that has a '__set__' method. If yes, call this class attribute's __set__ method with instance and value as arguments. This is the second part part of the "descriptor protocol", as used by the property type.
* else, add the attribute's name and value in the instance's __dict__


As I said, this is a somehow simplified description of the process - I skipped the parts about __slots__, __getattribute__ and __setattr__, as well as the part about how function class attributes become methods. But this should be enough to get an idea of what's going on.

In your above case, you defined a "foo" property class attribute. The property type implements both __get__ and __set__, but you only defined a callback for the __get__ method (the function you decorated with 'property'), so when you try to rebind "foo", the default property type's __set__ implementation is invoked, which behaviour is to forbid setting the attribute. If you want a settable property, you have to provide a setter too.

Now if you want a "replaceable" property-like attribute, you could define your own computed attribute (aka "descriptor") type _without_ a __set__ method:

class replaceableprop(object):
    def __init__(self, fget):
        self._fget = fget
    def __get__(self, instance, cls):
        if instance is None:
            return self
        return self._fget(instance)

@replaceableprop
def foo(self):
    # will add 'foo' into self.__dict__, s
    self.foo = self._some_time_consuming_operation()
    return self.foo


Another (better IMHO) solution is to use a plain property, and store the computed value as an implementation attribute :

@property
def foo(self):
  cached = self.__dict__.get('_foo_cache')
  if cached is None:
      self._foo_cache = cached = self._some_time_consuming_operation()
  return cached


 Sometimes one can set them, sometimes
not, and I can't quite tell the two situations apart.  It's all
very confusing to the Noob.  (I'm sure this is all documented
*somewhere*, but this does not make using attributes any more
intuitive or straightforward.  I'm also sure that *eventually*,
with enough Python experience under one's belt, this all becomes
second nature.  My point is that Python attributes are not as
transparent and natural to the uninitiated as some of you folks
seem to think.)

I agree that the introduction of the descriptor protocol added some more complexity to an already somehow unusual model object.

HTH.
--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to