Mark Lutz

Python’s New-Style Inheritance Algorithm

This article takes a brief look at the inheritance search mechanism in the Python programming language. Like some other aspects of Python today, this mechanism varies per line: inheritance has grown much more convoluted in 3.X, though 2.X users still have a choice in the matter. To truly understand the current state of affairs, then, we need to begin our story in simpler times.

Classic Inheritance

Once upon a time (well, in 2.X’s default and still widely used classic classes), Python attribute inheritance—the object.name lookup at the heart of object-oriented code—was fairly simple. It essentially boiled down to this:

Attribute name references search the instance, its class, and the class’s superclasses depth-first and left-to-right, and use the first occurrence found along the way. Attribute assignments normally store in the target object itself.

And that’s it. The reference search may be kicked off from either an instance or a class, and there are special cases for __getattr__ (run if the lookup failed to find a name) and __setattr__ (run for all attribute assignments), but the procedure is by and large straightforward.

New-Style Inheritance

In new-style classes—an option in 2.X and mandated in 3.X—inheritance is richer but substantially more complex, potentially requiring knowledge of advanced topics to accurately resolve an attribute name’s meaning, including descriptors, metaclasses, and the linearized class-tree paths known as MROs. We won’t delve into those prerequisite topics here, but the following is a cursory overview of the algorithm used, taken from the newly released Learning Python, 5th Edition, where you’ll find new and more complete coverage.

To look up an attribute name:

  1. From an instance I, search the instance, its class, and its superclasses, as follows:
    1. Search the __dict__ of all classes on the __mro__ found at I’s __class__
    2. If a data descriptor was found in step a, call its __get__ and exit
    3. Else, return a value in the __dict__ of the instance I
    4. Else, call a nondata descriptor or return a value found in step a
  2. From a class C, search the class, its superclasses, and its metaclasses tree, as follows:
    1. Search the __dict__ of all metaclasses on the __mro__ found at C’s __class__
    2. If a data descriptor was found in step a, call its __get__ and exit
    3. Else, call a descriptor or return a value in the __dict__ of a class on C’s own __mro__
    4. Else, call a nondata descriptor or return a value found in step a
  3. In both rule 1 and 2, built-in operations essentially use just step a sources for their implicit name look up (described further in the book).

Name sources in this procedure are attempted in order, either as numbered or per their left-to-right order in “or” conjunctions. On top of all this, method __getattr__ may be run if defined when an attribute is not found; method __getattribute__ may be run for every attribute fetch; and the implied object superclass provides some defaults at the top of every class and metaclass tree (that is, at the end of every MRO).

Read more…

Comment