[ Team LiB ] |
22.6 Multiple InheritanceIn 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]
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 ] |