|I l@ve RuBoard|
5.4 Calling a Superclass _ _init_ _ Method if It Exists
Credit: Alex Martelli
There are several ways to perform this task. In a Python 2.2 new-style class, the built-in super function makes it easy (as long as all superclass _ _init_ _ methods also use super similarly):
class NewStyleOnly(A, B, C): def _ _init_ _(self): super(NewStyleOnly, self)._ _init_ _( ) # Subclass-specific initialization follows
For classic classes, we need an explicit loop over the superclasses, but we can still choose different ways to handle the possibility that each superclass may or may not have an _ _init_ _ method. The most intuitive approach is to "Look Before You Leap" (LBYL), i.e., check for existence before calling. While in many other cases LBYL has problems, in this specific case it doesn't, so we use it because it is the simplest approach:
class LookBeforeYouLeap(X, Y, Z): def _ _init_ _(self): for base in self_ _class_ _._ _bases_ _: if hasattr(base, '_ _init_ _'): base._ _init_ _(self) # Subclass-specific initialization follows
Often, we want to call a method on an instance (or class) if and only if that method exists. Otherwise, we do nothing or default to another action. For example, this often applies to the _ _init_ _ method of superclasses, since Python does not automatically call this method if it exists. A direct call of X._ _init_ _(self) (including approaches such as those in Recipe 5.5) works only if base class X defines an _ _init_ _ method. We may, however, want to make our subclass independent from such a superclass implementation detail. Typically, the coupling of a subclass to its base classes is pretty tight; loosening it is not a bad idea, if it is feasible and inexpensive.
In Python 2.2's new-style object model, the built-in super function provides the simplest, fastest, and most direct solution, as long as all superclasses are also new-style and use super similarly. Note that all new-style classes have an _ _init_ _ method because they all subclass object, and object defines _ _init_ _ as a do-nothing function that accepts and ignores its arguments. Therefore, all new-style classes have an _ _init_ _ method, either by inheritance or by override.
More generally, however, we may want to hand-craft another solution, which will help us for classic classes, mixtures of new-style and classic classes, and other methods that may or may not be present in each given superclass. Even though this recipe is about _ _init_ _, its ideas can clearly apply to other cases in which we want to call all the superclass implementations of any other given method. We then have a choice of three general categories of approaches:
The solution shows the first approach, which is the simplest and most appropriate for the common case of _ _init_ _ in a multiple, classic-class inheritance. (The recipe's code works just as well with single inheritance, of course. Indeed, as a special case, it works fine even when used in a class without any bases.) Using the LBYL approach here has the great advantage of being obvious. Note that the built-in hasattr function implements proper lookup in the bases of our bases, so we need not worry about that. As a general idiom, LBYL often has serious issues, but they don't apply in this specific case. For example, LBYL can interrupt an otherwise linear control flow with readability-damaging checks for rare circumstances. With LBYL, we also run the risk that the condition we're checking might change between the moment when we look and the moment when we leap (e.g., in a multithreaded scenario). If you ever have to put locks and safeguards bracketing the look and the leap, it's best to choose another approach. But this recipe's specific case is one of the few in which LBYL is okay.
class EasierToAskForgiveness_Naive(X, Y, Z): def _ _init_ _(self): for base in self_ _class_ _._ _bases_ _: try: base._ _init_ _(self) except AttributeError: pass # Subclass-specific initialization follows
While EAFP is a good general approach and very Pythonic, we still need to be careful to catch only the specific exception we're expecting from exactly where we're expecting it. The previous code is not accurate and careful enough. If base._ _init_ _ exists but fails, and an AttributeError is raised because of an internal logic problem, typo, etc., _ _init_ _ will mask it. It's not hard to fashion a much more robust version of EAFP:
class EasierToAskForgiveness_Robust(X, Y, Z): def _ _init_ _(self): for base in self_ _class_ _._ _bases_ _: try: fun = base._ _init_ _ except AttributeError: pass else: fun(self) # Subclass-specific initialization follows
The _Robust variant is vastly superior, since it separates the subtask of accessing the base._ _init_ _ callable object (unbound method object) from the task of calling it. Only the access to the callable object is protected in the try/except. The call happens only when no exception was seen (which is what the else clause is for in the try/except statement), and if executing the call raises any exceptions, they are correctly propagated.
class HomogenizeDifferentCases1(X, Y, Z): def _ _init_ _(self): def doNothing(obj): pass for base in self_ _class_ _._ _bases_ _: fun = getattr(base, '_ _init_ _', doNothing) fun(self) # Subclass-specific initialization follows
For lambda fanatics, here is an alternative implementation:
class HomogenizeDifferentCases2(X, Y, Z): def _ _init_ _(self): for base in self_ _class_ _._ _bases_ _: fun = getattr(base, '_ _init_ _', lambda x: None) fun(self) # Subclass-specific initialization follows
Again, this is a good general approach (in Python and more generally in programming) that often leads to simpler, more linear code (and sometimes to better speed). Instead of checking for possible special cases, we do some preprocessing that ensures we are in regular cases, then we proceed under full assumption of regularity. The sentinel idiom in searches is a good example of HDC in a completely different context, as is the Null Object design pattern (see Recipe 5.24). The only difference between the two HDC examples described here is how the do-nothing callable is built: the first uses a simple nested function with names that make its role (or, perhaps, nonrole) totally obvious, while the other uses a lambda form. The choice between them is strictly a style issue.
5.4.4 See Also
|I l@ve RuBoard|