I l@ve RuBoard Previous Section Next Section

6.5 Inheritance Searches Namespace Trees

The whole point of a namespace tool like the class statement is to support name inheritance. In Python, inheritance happens when an object is qualified, and involves searching an attribute definition tree (one or more namespaces). Every time you use an expression of the form object.attr where object is an instance or class object, Python searches the namespace tree at and above object, for the first attr it can find. Because lower definitions in the tree override higher ones, inheritance forms the basis of specialization.

6.5.1 Attribute Tree Construction

Figure 6.4 sketches the way namespace trees are constructed. In general:

The net result is a tree of attribute namespaces, which grows from an instance, to the class it was generated from, to all the superclasses listed in the class headers. Python searches upward in this tree from instances to superclasses, each time you use qualification to fetch an attribute name from an instance object.[3]

[3] This description isn't 100% complete, because instance and class attributes can also be created by assigning to objects outside class statements. But that's less common and sometimes more error prone (changes aren't isolated to class statements). In Python all attributes are always accessible by default; we talk about privacy later in this chapter.

Figure 6.4. Namespaces tree construction and inheritance
figs/lpy_0604.gif

6.5.2 Specializing Inherited Methods

The tree-searching model of inheritance we just described turns out to be a great way to specialize systems. Because inheritance finds names in subclasses before it checks superclasses, subclasses can replace default behavior by redefining the superclass's attributes. In fact, you can build entire systems as hierarchies of classes, which are extended by adding new external subclasses rather than changing existing logic in place.

The idea of overloading inherited names leads to a variety of specialization techniques. For instance, subclasses may replace inherited names completely, provide names a superclass expects to find, and extend superclass methods by calling back to the superclass from an overridden method. We've already seen replacement in action; here's an example that shows how extension works:

>>> class Super:
...     def method(self):
...         print 'in Super.method'
... 
>>> class Sub(Super):
...     def method(self):                       # override method
...         print 'starting Sub.method'         # add actions here
...         Super.method(self)                  # run default action
...         print 'ending Sub.method'
... 

Direct superclass method calls are the crux of the matter here. The Sub class replaces Super's method function with its own specialized version. But within the replacement, Sub calls back to the version exported by Super to carry out the default behavior. In other words, Sub.method just extends Super.method's behavior, rather than replace it completely:

>>> x = Super()            # make a Super instance
>>> x.method()             # runs Super.method
in Super.method

>>> x = Sub()              # make a Sub instance
>>> x.method()             # runs Sub.method, which calls Super.method
starting Sub.method
in Super.method
ending Sub.method

Extension is commonly used with constructors; since the specially named __ init __ method is an inherited name, only one is found and run when an instance is created. To run superclass constructors, subclass __ init __ methods should call superclass __ init __ methods, by qualifying classes (e.g., Class. __ init __ (self, ...)).

Extension is only one way to interface with a superclass; the following shows subclasses that illustrate these common schemes:

class Super:
    def method(self):
        print 'in Super.method'    # default
    def delegate(self):
        self.action()              # expected

class Inheritor(Super):
    pass

class Replacer(Super):
    def method(self):
        print 'in Replacer.method'

class Extender(Super):
    def method(self):
        print 'starting Extender.method'
        Super.method(self)
        print 'ending Extender.method'

class Provider(Super):
    def action(self):
        print 'in Provider.action'

if __name__ == '__main__':
    for klass in (Inheritor, Replacer, Extender):
        print '\n' + klass.__name__ + '...'
        klass().method()
    print '\nProvider...'
    Provider().delegate()

A few things are worth pointing out here: the self-test code at the end of this example creates instances of three different classes; because classes are objects, you can put them in a tuple and create instances generically (more on this idea later). Classes also have the special __ name _ _ attribute as modules; it's just preset to a string containing the name in the class header. When you call the delegate method though a Provider instance, Python finds the action method in Provider by the usual tree search: inside the Super delegate method, self references a Provider instance.

% python specialize.py

Inheritor...
in Super.method

Replacer...
in Replacer.method

Extender...
starting Extender.method
in Super.method
ending Extender.method

Provider...
in Provider.action
I l@ve RuBoard Previous Section Next Section