77
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:
1. Check for attribute existence with
hasattr
before the otherwise normal call.
2. Try the call (or the attribute fetching with
getattr
) and catch the error, if any.
3. 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.