Learning Python - Mark Lutz [416]
C:\misc> c:\python26\python testmixin.py
name __doc__=<> name __init__=<> name __module__=<> name __str__=<> name data1=spam name data2=eggs name data3=42 name ham= name spam= > In Python 3.0, more attributes are displayed because all classes are “new-style” and inherit names from the implied object superclass (more on this in Chapter 31). Because so many names are inherited from the default superclass, I’ve omitted many here; run this on your own for the full listing: C:\misc> c:\python30\python testmixin.py name __class__=<> name __delattr__=<> name __dict__=<> name __doc__=<> name __eq__=<> ...more names omitted... name __repr__=<> name __setattr__=<> name __sizeof__=<> name __str__=<> name __subclasshook__=<> name __weakref__=<> name data1=spam name data2=eggs name data3=42 name ham= name spam= > One caution here—now that we’re displaying inherited methods too, we have to use __str__ instead of __repr__ to overload printing. With __repr__, this code will loop—displaying the value of a method triggers the __repr__ of the method’s class, in order to display the class. That is, if the lister’s __repr__ tries to display a method, displaying the method’s class will trigger the lister’s __repr__ again. Subtle, but true! Change __str__ to __repr__ here to see this for yourself. If you must use __repr__ in such a context, you can avoid the loops by using isinstance to compare the type of attribute values against types.MethodType in the standard library, to know which items to skip. Listing attributes per object in class trees Let’s code one last extension. As it is, our lister doesn’t tell us which class an inherited name comes from. As we saw in the classtree.py example near the end of Chapter 28, though, it’s straightforward to climb class inheritance trees in code. The following mix-in class makes use of this same technique to display attributes grouped by the classes they live in—it sketches the full class tree, displaying attributes attached to each object along the way. It does so by traversing the inheritance tree from an instance’s __class__ to its class, and then from the class’s __bases__ to all superclasses recursively, scanning object __dicts__s along the way: # File lister.py, continued class ListTree: """ Mix-in that returns an __str__ trace of the entire class tree and all its objects' attrs at and above self; run by print(), str() returns constructed string; uses __X attr names to avoid impacting clients; uses generator expr to recurse to superclasses; uses str.format() to make substitutions clearer """ def __str__(self): self.__visited = {} return ' self.__class__.__name__, id(self), self.__attrnames(self, 0), self.__listclass(self.__class__, 4)) def __listclass(self, aClass, indent): dots = '.' * indent if aClass in self.__visited: return '\n{0} dots, aClass.__name__, id(aClass)) else: self.__visited[aClass] = True genabove = (self.__listclass(c, indent+4) for c in aClass.__bases__) return '\n{0} dots, aClass.__name__, id(aClass), self.__attrnames(aClass, indent), ''.join(genabove), dots) def __attrnames(self, obj, indent): spaces = ' ' * (indent + 4) result = '' for attr in sorted(obj.__dict__): if attr.startswith('__') and attr.endswith('__'): result += spaces + '{0}=<>\n'.format(attr) else: result += spaces + '{0}={1}\n'.format(attr, getattr(obj, attr))