[Python-Dev] Descriptor write-up [Draft: Please comment]

Phillip J. Eby pje@telecommunity.com
Sun, 01 Jun 2003 06:32:36 -0400


At 04:32 AM 6/1/03 -0400, Raymond Hettinger wrote:
>In the spirit of Michele Simionato's write-up on method resolution order, 
>I've written up almost everything I know about
>descriptors:
>http://tinyurl.com/d63d
>
>Key features:
>* Defines a descriptor
>* Shows the protocol and how it is called
>* Shows a handmade descriptor
>* Then covers functions, properties, static and class methods
>* Gives examples of each
>* Suggests when each would be useful
>* Gives a pure python definition of each
>* Provides exact references into C source to save efforts in hunting down 
>details
>
>I would like to hammer this into something really useful.  So, any and all 
>suggestions are welcome.

It would be a good idea to add some information about "data" and "non-data" 
descriptors, and the differences of how their attribute lookup is 
processed.  I recently posted here about the "attribute lookup process" or 
something to that effect, which covered this.  Understanding data vs. 
non-data descriptors is important if you want to do pretty much anything 
with descriptors beyond what property() does.

This section:

"Alternatively, a descriptor is invoked automatically upon attribute 
access.  For example, obj.a looks up a in the dictionary of obj.  If obj 
defines the method __get__, then it is automatically invoked if obj is a 
new style class or object."

isn't accurate.  I think you meant to say that if 'a' defines __get__, then 
it's invoked.  But this isn't accurate either.  If obj.__dict__ has an 'a' 
entry, and the descriptor is a non-data descriptor, __get__ will be ignored 
and the contents of obj.__dict__ will be returned.  (One interesting effect 
of this is that you can create a non-data descriptor with a __get__ that 
performs a computation and stashes the result in the instance dictionary - 
from then on the computed value is returned.)

The Python code you have for the algorithm is also incorrect.  Here's a 
more accurate depiction of the process.  It's not a straight translation of 
PyGeneric_GetAttr, but an attempt to do as you have done, i.e. write a pure 
Python __getattribute__ with equivalent semantics.

     def __getattribute__(self, key):

         dict = object.__getattribute__(self, '__dict__')

         try:
             # Does the class have a descriptor for this attribute?
             descr = getattr(type(self),key)

         except AttributeError:
             # No, just use what's in the dictionary
             try:
                 return dict[key]
             except KeyError:
                 raise AttributeError


         if hasattr(descr,'__set__') and hasattr(descr,'__get__'):
              # It's a data descriptor, so use the get method
              return descr.__get__(self,type(self))

         # non-data descriptor, use __dict__ first, *then* __get__
         try:
             return dict[key]
         except KeyError:
             if hasattr(descr, '__get__'):
                 return descr.__get__(self, type(self))

         # not a descriptor, return it as-is
         return descr

As you can see, it's a bit more complex than your writeup implies.  :)