Learning Python - Mark Lutz [415]
Super.__init__(self)
self.data2 = 'eggs' # More instance attrs
self.data3 = 42
def spam(self): # Define another method here
pass
if __name__ == '__main__':
X = Sub()
print(X) # Run mixed-in __str__
Here, Sub inherits names from both Super and ListInstance; 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 ListInstance (in this case, this script’s output is the same under both Python 3.0 and 2.6, except for object addresses):
C:\misc> C:\python30\python testmixin.py
name data2=eggs name data3=42 > ListInstance works in any class it’s mixed into because self refers to an instance of the subclass that pulls this class in, whatever that may be. In a sense, mix-in classes are the class equivalent of modules—packages of methods useful in a variety of clients. For example, here is Lister working again in single-inheritance mode on a different class’s instances, with import and attributes set outside the class: >>> import lister >>> class C(lister.ListInstance): pass ... >>> x = C() >>> x.a = 1; x.b = 2; x.c = 3 >>> print(x) name b=2 name c=3 > Besides the utility they provide, mix-ins optimize code maintenance, like all classes do. For example, if you later decide to extend ListInstance’s __str__ to also print all the class attributes that an instance inherits, you’re safe; because it’s an inherited method, changing __str__ automatically updates the display of each subclass that imports the class and mixes it in. Since it’s now officially “later,” let’s move on to the next section to see what such an extension might look like. Listing inherited attributes with dir As it is, our Lister mix-in displays instance attributes only (i.e., names attached to the instance object itself). It’s trivial to extend the class to display all the attributes accessible from an instance, though—both its own and those it inherits from its classes. The trick is to use the dir built-in function instead of scanning the instance’s __dict__ dictionary; the latter holds instance attributes only, but the former also collects all inherited attributes in Python 2.2 and later. The following mutation codes this scheme; I’ve renamed it to facilitate simple testing, but if this were to replace the original version, all existing clients would pick up the new display automatically: # File lister.py, continued class ListInherited: """ Use dir() to collect both instance attrs and names inherited from its classes; Python 3.0 shows more names than 2.6 because of the implied object superclass in the new-style class model; getattr() fetches inherited names not in self.__dict__; use __str__, not __repr__, or else this loops when printing bound methods! """ def __str__(self): return ' self.__class__.__name__, # My class's name id(self), # My address self.__attrnames()) # name=value list def __attrnames(self): result = '' for attr in dir(self): # Instance dir() if attr[:2] == '__' and attr[-2:] == '__': # Skip internals result += '\tname %s=<>\n' % attr else: result += '\tname %s=%s\n' % (attr, getattr(self, attr)) return result Notice that this code skips __X__ names’ values; most of these are internal names that we don’t generally care about in a generic listing like this. This version also must use the getattr built-in function to fetch attributes by name string instead of using instance attribute dictionary indexing—getattr employs the inheritance search protocol, and some of the names we’re listing here are not stored on the instance itself. To test the new version, change the testmixin.py file to use this new class instead: class Sub(Super, ListInherited): # Mix in a __str__ This file’s output varies per release. In Python 2.6, we get the following; notice the name mangling at work in the lister’s method name (I shortened