How to automate accessor definition?

Bruno Desthuilliers bruno.42.desthuilliers at websiteburo.invalid
Mon Mar 22 16:44:01 CET 2010


kj a écrit :
> In <mailman.1030.1269194878.23598.python-list at python.org> Dennis Lee Bieber <wlfraed at ix.netcom.com> writes:
> 
>> On Sun, 21 Mar 2010 16:57:40 +0000 (UTC), kj <no.email at 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.



More information about the Python-list mailing list