Credit: Alex Martelli
You want to ensure that _ _init_ _
is
called for all superclasses that define it, and Python does not do
this automatically.
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:
Check for attribute existence with hasattr
before
the otherwise normal call.
Try the call (or the attribute fetching with
getattr
) and catch the error, if any.
Use getattr
to return the desired attribute, or
else a do-nothing function (more generally, a callable object with
suitable default functionality) if the attribute does not exist, then
proceed by calling whatever callable is returned.
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.
The second approach is known as “Easier to Ask Forgiveness than Permission” (EAFP). The following naive variant of it is somewhat fragile:
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.
Separating the acquisition of the callable from calling it leads us to the third approach, known as “Homogenize Different Cases” (HDC). It’s best implemented with a small do-nothing local function:
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.
Recipe 5.5 and Recipe 5.24.