Inheritance

Of course, the whole point of the namespace created by the class statement is to support name inheritance. This section expands on some of the mechanisms and roles of attribute inheritance in Python.

As we’ve seen, in Python, inheritance happens when an object is qualified, and it 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 from bottom to top, beginning with object, looking for the first attr it can find. This includes references to self attributes in your methods. Because lower definitions in the tree override higher ones, inheritance forms the basis of specialization.

Attribute Tree Construction

Figure 29-1 summarizes the way namespace trees are constructed and populated with names. Generally:

  • Instance attributes are generated by assignments to self attributes in methods.

  • Class attributes are created by statements (assignments) in class statements.

  • Superclass links are made by listing classes in parentheses in a class statement header.

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

Program code creates a tree of objects in memory to be searched by attribute inheritance. Calling a class creates a new instance that remembers its class, running a class statement creates a new class, and superclasses are listed in parentheses in the class statement header. Each attribute reference triggers a new bottom-up tree search—even references to self attributes within a class’s methods.

Figure 29-1. Program code creates a tree of objects in memory to be searched by attribute inheritance. Calling a class creates a new instance that remembers its class, running a class statement creates a new class, and superclasses are listed in parentheses in the class statement header. Each attribute reference triggers a new bottom-up tree search—even references to self attributes within a class’s methods.

Specializing Inherited Methods

The tree-searching model of inheritance 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 their superclasses’ attributes. In fact, you can build entire systems as hierarchies of classes, which you extend by adding new external subclasses rather than changing existing logic in place.

The idea of redefining inherited names leads to a variety of specialization techniques. For instance, subclasses may replace inherited attributes completely, provide attributes that a superclass expects to find, and extend superclass methods by calling back to the superclass from an overridden method. We’ve already seen some of these patterns in action; here’s a self-contained example of extension at work:

>>> 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 replacing 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, calls Super.method
starting Sub.method
in Super.method
ending Sub.method

This extension coding pattern is also commonly used with constructors; see the section Methods for an example.

Class Interface Techniques

Extension is only one way to interface with a superclass. The file shown in this section, specialize.py, defines multiple classes that illustrate a variety of common techniques:

Super

Defines a method function and a delegate that expects an action in a subclass.

Inheritor

Doesn’t provide any new names, so it gets everything defined in Super.

Replacer

Overrides Super’s method with a version of its own.

Extender

Customizes Super’s method by overriding and calling back to run the default.

Provider

Implements the action method expected by Super’s delegate method.

Study each of these subclasses to get a feel for the various ways they customize their common superclass. Here’s the file:

class Super:
    def method(self):
        print('in Super.method')           # Default behavior
    def delegate(self):
        self.action()                      # Expected to be defined

class Inheritor(Super):                    # Inherit method verbatim
    pass

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

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

class Provider(Super):                     # Fill in a required method
    def action(self):
        print('in Provider.action')

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

A few things are worth pointing out here. First, notice how the self-test code at the end of this example creates instances of three different classes in a for loop. Because classes are objects, you can store them in a tuple and create instances generically with no extra syntax (more on this idea later). Classes also have the special __name__ attribute, like modules; it’s preset to a string containing the name in the class header. Here’s what happens when we run the file:

% 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

Abstract Superclasses

Of the prior example’s classes, Provider may be the most crucial to understand. When we call the delegate method through a Provider instance, two independent inheritance searches occur:

  1. On the initial x.delegate call, Python finds the delegate method in Super by searching the Provider instance and above. The instance x is passed into the method’s self argument as usual.

  2. Inside the Super.delegate method, self.action invokes a new, independent inheritance search of self and above. Because self references a Provider instance, the action method is located in the Provider subclass.

This “filling in the blanks” sort of coding structure is typical of OOP frameworks. In a more realistic context, the method filled in this way might handle an event in a GUI, provide data to be rendered as part of a web page, process a tag’s text in an XML file, and so on—your subclass provides specific actions, but the framework handles the rest of the overall job.

At least in terms of the delegate method, the superclass in this example is what is sometimes called an abstract superclass—a class that expects parts of its behavior to be provided by its subclasses. If an expected method is not defined in a subclass, Python raises an undefined name exception when the inheritance search fails.

Class coders sometimes make such subclass requirements more obvious with assert statements, or by raising the built-in NotImplementedError exception with raise statements. We’ll study statements that may trigger exceptions in depth in the next part of this book; as a quick preview, here’s the assert scheme in action:

class Super:
    def delegate(self):
        self.action()
    def action(self):
        assert False, 'action must be defined!'      # If this version is called

>>> X = Super()
>>> X.delegate()
AssertionError: action must be defined!

We’ll meet assert in Chapter 33 and Chapter 34; in short, if its first expression evaluates to false, it raises an exception with the provided error message. Here, the expression is always false so as to trigger an error message if a method is not redefined, and inheritance locates the version here. Alternatively, some classes simply raise a NotImplementedError exception directly in such method stubs to signal the mistake:

class Super:
    def delegate(self):
        self.action()
    def action(self):
        raise NotImplementedError('action must be defined!')

>>> X = Super()
>>> X.delegate()
NotImplementedError: action must be defined!

For instances of subclasses, we still get the exception unless the subclass provides the expected method to replace the default in the superclass:

>>> class Sub(Super): pass

>>> X = Sub()
>>> X.delegate()
NotImplementedError: action must be defined!

>>> class Sub(Super):
        def action(self): print('spam')

>>> X = Sub()
>>> X.delegate()
spam

For a somewhat more realistic example of this section’s concepts in action, see the “Zoo animal hierarchy” exercise (Exercise 8) at the end of Chapter 32, and its solution in “Part VI, Classes and OOP” in Appendix D. Such taxonomies are a traditional way to introduce OOP, but they’re a bit removed from most developers’ job descriptions (with apologies to any readers who happen to work at the zoo!).

Abstract superclasses in Python 3.X and 2.6+: Preview

As of Python 2.6 and 3.0, the prior section’s abstract superclasses (a.k.a. “abstract base classes”), which require methods to be filled in by subclasses, may also be implemented with special class syntax. The way we code this varies slightly depending on the version. In Python 3.X, we use a keyword argument in a class header, along with special @ decorator syntax, both of which we’ll study in detail later in this book:

from abc import ABCMeta, abstractmethod

class Super(metaclass=ABCMeta):
    @abstractmethod
    def method(self, ...):
        pass

But in Python 2.6 and 2.7, we use a class attribute instead:

class Super:
    __metaclass__ = ABCMeta
    @abstractmethod
    def method(self, ...):
        pass

Either way, the effect is the same—we can’t make an instance unless the method is defined lower in the class tree. In 3.X, for example, here is the special syntax equivalent of the prior section’s example:

>>> from abc import ABCMeta, abstractmethod
>>>
>>> class Super(metaclass=ABCMeta):
        def delegate(self):
            self.action()
        @abstractmethod
        def action(self):
            pass

>>> X = Super()
TypeError: Can't instantiate abstract class Super with abstract methods action

>>> class Sub(Super): pass

>>> X = Sub()
TypeError: Can't instantiate abstract class Sub with abstract methods action

>>> class Sub(Super):
        def action(self): print('spam')

>>> X = Sub()
>>> X.delegate()
spam

Coded this way, a class with an abstract method cannot be instantiated (that is, we cannot create an instance by calling it) unless all of its abstract methods have been defined in subclasses. Although this requires more code and extra knowledge, the potential advantage of this approach is that errors for missing methods are issued when we attempt to make an instance of the class, not later when we try to call a missing method. This feature may also be used to define an expected interface, automatically verified in client classes.

Unfortunately, this scheme also relies on two advanced language tools we have not met yet—function decorators, introduced in Chapter 32 and covered in depth in Chapter 39, as well as metaclass declarations, mentioned in Chapter 32 and covered in Chapter 40—so we will finesse other facets of this option here. See Python’s standard manuals for more on this, as well as precoded abstract superclasses Python provides.



[60] Two fine points here: first, this description isn’t 100% complete, because we can also create instance and class attributes by assigning them to objects outside class statements—but that’s a much less common and sometimes more error-prone approach (changes aren’t isolated to class statements). In Python, all attributes are always accessible by default. We’ll talk more about attribute name privacy in Chapter 30 when we study __setattr__, in Chapter 31 when we meet __X names, and again in Chapter 39, where we’ll implement it with a class decorator.

Second, as also noted in Chapter 27, the full inheritance story grows more convoluted when advanced topics such as metaclasses and descriptors are added to the mix—and we’re deferring a formal definition until Chapter 40 for this reason. In common usage, though, it’s simply a way to redefine, and hence customize, behavior coded in classes.