[Python-Dev] Attribute lookup (was Re: python-dev Summary for 2003-05-01 through 2003-05-15)

Phillip J. Eby pje@telecommunity.com
Sun, 18 May 2003 22:18:43 -0400


At 09:58 PM 5/18/03 -0400, Aahz wrote:
>[Normally I send my corrections to Brett privately, but since I'm taking
>a whack at attribute lookup, I figured this ought to be public.]
>
>On Sun, May 18, 2003, Brett C. wrote:
> >
> > The only thing I would like help with this summary is if someone knows
> > the attribute lookup order (instance, class, class descriptor, ...) off
> > the top of their heads, can you let me know?  If not I can find it out
> > by going through the docs but I figure someone out there has to know it
> > by heart and any possible quirks (like whether descriptors take
> > precedence over non-descriptor attributes).
>
>This gets real tricky.  For simple attributes of an instance, the order
>is instance, class/type, and base classes of the class/type (but *not*
>the metaclass).  However, method resolution of the special methods goes
>straight to the class.  Finally, if an attribute is found on the
>instance, a search goes through the hierarchy to see whether a set
>descriptor overrides (note specifically that it's a set descriptor;
>methods are implemented using get descriptors).
>
>I *think* I have this right, but I'm sure someone will correct me if I'm
>wrong.

Here's the algorithm in a bit more detail:

1. First, the class/type and its bases are searched, checking dictionaries 
only.

2. If the object found is a "data descriptor"  (i.e. has a type with a 
non-null tp_descr_set pointer, which is closely akin to whether the 
descriptor has a '__set-_' attribute), then the data descriptor's __get__ 
method is invoked.

3. If the object is not found, or not a data descriptor, the instance 
dictionary is checked.  If the attribute isn't in the instance dictionary, 
then the descriptor's __get__ method is invoked (assuming a descriptor was 
found).

4. Invoke __getattr__ if present.

(Note that replacing __getattribute__ *replaces* this entire algorithm.)

Also note that special methods are *not* handled specially here.  The 
behavior Aahz is referring to is that slots (e.g. tp_call) on new-style 
types do not retrieve an instance attribute; they are based purely on 
class-level data.  So, although you *can* override the values in an 
instance, they have no effect on the class behavior.  E.g.:

 >>> class Foo(object):
         def __call__(self,*args):
                 print "foo",args


 >>> f=Foo()
 >>> f.__call__ = 'spam'
 >>> f.__call__
'spam'
 >>> f()
foo ()
 >>>

Notice that the behavior of the instance '__call__' attribute does not 
affect the class-level definition of '__call__'.

To recast the algorithm as a precedence order:

1. Data descriptors (ones with tp_descr_set/__set__) found in the type 
__mro__  (note that this includes __slots__, property(), and custom 
descriptors)

2. Instance attributes found in ob.__dict__

3. Non-data descriptors, such as methods, or any other object found in the 
type __mro__ under that name

4. __getattr__