Learning Python - Mark Lutz [427]
Since it is most likely that the programmer meant that C should override A in this case, though, new-style classes visit C first. Otherwise, C could be essentially pointless in a diamond context: it could not customize A and would be used only for names unique to C.
Explicit conflict resolution
Of course, the problem with assumptions is that they assume things. If this search order deviation seems too subtle to remember, or if you want more control over the search process, you can always force the selection of an attribute from anywhere in the tree by assigning or otherwise naming the one you want at the place where the classes are mixed together:
>>> class A:
attr = 1 # Classic
>>> class B(A):
pass
>>> class C(A):
attr = 2
>>> class D(B, C):
attr = C.attr # Choose C, to the right
>>> x = D()
>>> x.attr # Works like new-style (all 3.0)
2
Here, a tree of classic classes is emulating the search order of new-style classes: the assignment to the attribute in D picks the version in C, thereby subverting the normal inheritance search path (D.attr will be lowest in the tree). New-style classes can similarly emulate classic classes by choosing the attribute above at the place where the classes are mixed together:
>>> class A(object):
attr = 1 # New-style
>>> class B(A):
pass
>>> class C(A):
attr = 2
>>> class D(B, C):
attr = B.attr # Choose A.attr, above
>>> x = D()
>>> x.attr # Works like classic (default 2.6)
1
If you are willing to always resolve conflicts like this, you can largely ignore the search order difference and not rely on assumptions about what you meant when you coded your classes.
Naturally, attributes picked this way can also be method functions—methods are normal, assignable objects:
>>> class A:
def meth(s): print('A.meth')
>>> class C(A):
def meth(s): print('C.meth')
>>> class B(A):
pass
>>> class D(B, C): pass # Use default search order
>>> x = D() # Will vary per class type
>>> x.meth() # Defaults to classic order in 2.6
A.meth
>>> class D(B, C): meth = C.meth # Pick C's method: new-style (and 3.0)
>>> x = D()
>>> x.meth()
C.meth
>>> class D(B, C): meth = B.meth # Pick B's method: classic
>>> x = D()
>>> x.meth()
A.meth
Here, we select methods by explicitly assigning to names lower in the tree. We might also simply call the desired class explicitly; in practice, this pattern might be more common, especially for things like constructors:
class D(B, C):
def meth(self): # Redefine lower
...
C.meth(self) # Pick C's method by calling
Such selections by assignment or call at mix-in points can effectively insulate your code from this difference in class flavors. Explicitly resolving the conflicts this way ensures that your code won’t vary per Python version in the future (apart from perhaps needing to derive classes from object or a built-in type for the new-style tools in 2.6).
* * *
Note
Even without the classic/new-style class divergence, the explicit method resolution technique shown here may come in handy in multiple inheritance scenarios in general. For instance, if you want part of a superclass on the left and part of a superclass on the right, you might need to tell Python which same-named attributes to choose by using explicit assignments in subclasses. We’ll revisit this notion in a “gotcha” at the end of this chapter.
Also note that diamond inheritance patterns might be more problematic in some cases than I’ve implied here (e.g., what if B and C both have required constructors that call to the constructor in A?). Since such contexts are rare in real-world Python, we