DekGenius.com
[ Team LiB ] Previous Section Next Section

22.6 Multiple Inheritance

In the class statement, more than one superclass can be listed in parentheses in the header line. When you do this, you use something called multiple inheritance—the class and its instances inherit names from all listed superclasses.

When searching for an attribute, Python searches superclasses in the class header from left to right until a match is found. Technically, the search proceeds depth-first all the way to the top, and then left to right, since any of the superclasses may have superclasses of its own.

In general, multiple inheritance is good for modeling objects that belong to more than one set. For instance, a person may be an engineer, a writer, a musician, and so on, and inherit properties from all such sets.

Perhaps the most common way multiple inheritance is used is to "mix in" general-purpose methods from superclasses. Such superclasses are usually called mixin classes—they provide methods you add to application classes by inheritance. For instance, Python's default way to print a class instance object isn't incredibly useful:

>>> class Spam:
...     def __init__(self):               # No __repr__
...         self.data1 = "food"
...
>>> X = Spam(  )
>>> print X                                   # Default: class, address
<__main__.Spam instance at 0x00864818>

As seen in the previous section on operator overloading, you can provide a __repr__ method to implement a custom string representation of your own. But rather than code a __repr__ in each and every class you wish to print, why not code it once in a general-purpose tool class, and inherit it in all your classes?

That's what mixins are for. The following code, file mytools.py, defines a mixin class called Lister that overloads the __repr__ method for each class that includes Lister in its header line. It simply scans the instance's attribute dictionary (remember, it's exported in __dict__) to build up a string showing the names and values of all instance attributes. Since classes are objects, Lister's formatting logic can be used for instances of any subclass; it's a generic tool.

Lister uses two special tricks to extract the instance's classname and address. Instances have a built-in __class__ attribute that references the class the instance was created from, and classes have a __name__ that is the name in the header, so self.__class__.__name__ fetches the name of an instance's class. You get the instance's memory address by calling the built-in id function, which returns any object's address (by definition, a unique object identifier):

###########################################
# Lister can be mixed-in to any class to
# provide a formatted print of instances
# via inheritance of __repr__ coded here;
# self is the instance of the lowest class;
###########################################

class Lister:
   def __repr__(self):
       return ("<Instance of %s, address %s:\n%s>" %
                         (self.__class__.__name__,     # My class's name
                          id(self),                       # My address
                          self.attrnames(  )) )           # name=value list
   def attrnames(self):
       result = ''
       for attr in self.__dict__.keys(  ):                # Instance namespace dict
           if attr[:2] == '__':
               result = result + "\tname %s=<built-in>\n" % attr
           else:
               result = result + "\tname %s=%s\n" % (attr, self.__dict__ [attr])
       return result

When derived from this class, instances display their attributes automatically when printed, which gives a bit more information than a simple address:

>>> from mytools import Lister
>>> class Spam(Lister):
...     def __init__(self):
...         self.data1 = 'food'
...
>>> x = Spam(  )
>>> x
<Instance of Spam, address 8821568:
        name data1=food
>

Now, the Lister class is useful for any class you write—even classes that already have a superclass. This is where multiple inheritance comes in handy: by adding (mixing in) Lister to the list of superclasses in a class header, you get its __repr__ for free, while still inheriting from the existing superclass. File testmixin.py demonstrates:

from mytools import Lister            # Get tool class

class Super:
    def __init__(self):                # superclass __init__
        self.data1 = "spam"

class Sub(Super, Lister):             # Mix-in a __repr__
    def __init__(self):                # Lister has access to self
        Super.__init__(self)
        self.data2 = "eggs"           # More instance attrs
        self.data3 = 42

if __name__ == "__main__":
    X = Sub(  )
    print X                           # Mixed-in repr

Here, Sub inherits names from both Super and Lister; it's a composite of its own names and names in both its superclasses. When you make a Sub instance and print it, you automatically get the custom representation mixed in from Lister:

C:\lp2e> python testmixin.py
<Instance of Sub, address 7833392:
        name data3=42
        name data2=eggs
        name data1=spam
>

Lister works in any class it's mixed into, because self refers to an instance of the subclass that pulls Lister in, whatever that may be. If you later decide to extend Lister's __repr__ to also print all the class attributes that an instance inherits, you're safe; because it's an inherited method, changing Lister.__repr__ automatically updates the display of each subclass that imports the class and mixes it in.[2]

[2] If you're curious how, flip back to Seciton 21.5.4 for hints. We saw there that classes have a built-in attribute called __bases__, which is a tuple of the class's superclass objects. A general-purpose class hierarchy lister or browser can traverse from an instance's __class__ to its class, and then from the class's __bases__ to all superclasses recursively, much like the classtree.py example shown earlier. In Python 2.2 and later it may be even simpler: the built-in dir function now includes inherited attribute names automatically. If you don't care about displaying tree structure, you might just scan the dir list instead of the dictionary keys list, and use getattr to fetch attributes by name string instead of dictionary key indexing. We'll rehash this idea in an exercise and its solution.

In some sense, mixin classes are the class equivalent of modules—packages of methods useful in a variety of clients. Here is Lister working again in single-inheritance mode, on a different class's instances; OOP is about code reuse:

>>> from mytools import Lister
>>> class x(Lister):
...     pass
...
>>> t = x(  )
>>> t.a = 1; t.b = 2; t.c = 3
>>> t
<Instance of x, address 7797696:
        name b=2
        name a=1
        name c=3

>

Mix-in classes are a powerful technique. In practice, multiple inheritance is an advanced tool and can become complicated if used carelessly or excessively. Like almost everything else in programming, it can be a useful device when applied well. We'll revisit this topic as a gotcha at the end of this part of the book. In Chapter 23, we'll also meet an option (new style classes) that modifies the search order for one special multiple inheritance case.

    [ Team LiB ] Previous Section Next Section