[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__