Python does not automatically call the __init__ (and __del__) methods of superclasses if subclasses define their own; explicit calling is needed, and it may be advisable to use a call-if-it-exists idiom.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | class LookBeforeYouLeap(X):
"look before you leap idiom for call-if-exists"
def __init__(self):
if hasattr(X, '__init__'):
X.__init__(self)
# subclass-specific initialization follows
class EasierToAskForgiveness1(X):
"easier to ask forgiveness idiom for call-if-exists"
# simpler variant
def __init__(self):
try: X.__init__(self)
except AttributeError: pass
# subclass-specific initialization follows
class EasierToAskForgiveness2(X):
"easier to ask forgiveness idiom for call-if-exists"
# more-robust variant
def __init__(self):
try: fun = getattr(X, '__init__')
except AttributeError: pass
else: fun(self)
# subclass-specific initialization follows
class HomogeneizeDifferentCases1(X):
"let's homogeinize different cases idiom for call-if-exists"
# function-variant
def __init__(self):
def doNothing(obj): pass
fun = getattr(X, '__init__', doNothing)
fun(self)
# subclass-specific initialization follows
class HomogeneizeDifferentCases2(X):
"let's homogeinize different cases idiom for call-if-exists"
# lambda-variant
def __init__(self):
fun = getattr(X, '__init__', lambda x: None)
fun(self)
# subclass-specific initialization follows
|
We often want to call some method on an instance, or class, if and only if that method exists -- else, do nothing, or default to some other action.
This, for example, often applies to the __init__ of our superclass[es], since Python does not automatically call the superclass[es]'s __init__ if any. A direct call of X.__init__(self) will only work if base class X does define an __init__ method, and we may want to make our subclass independent from such a superclass implementation detail.
We have a choice of idioms for this task, basically: [1] check for attribute existence with hasattr before the otherwise-normal call; [2] try the call (or the attribute reference with getattr) and catch the error, if any; [3] use getattr to return the desired attribute, or else a do-nothing function (more in general, some callable object with suitable default functionality) if the attribute does not exists, then, call whatever is thus returned.
This recipe exemplifies these three approaches (#2 and #3 in two variants each) for the common case of __init__. Each of these is easily generalized to multiple-inheritance -- just indent the snippet that's now __init__'s body into the body of a loop over all bases ("for X in self.__class__.__bases__:").
"Look before you leap" has the great advantage of being obvious -- clarity and simplicity are precious things, and programmers are often too clever for their own good. Note that the built-in hasattr() function DOES implement proper lookup in the bases of our bases, etc, so we need not worry about that. As a general idiom, "LBYL" has issues that don't really give problems in this case, such as interrupting an otherwise-linear control flow with readability-damaging checks for rarely occurring circumstances, or risking that the situation may change the moment when we "look" and the successive one where we "leap", e.g. in a multi-threaded scenario -- if you notice you have to put locks and safeguards around the look and the leap, it's probably worth looking for another approach among the following ones.
"Easier to ask forgiveness" is a good general approach in Python, but it still needs care to catch the specific exception we know we're expecting. EasierToAskForgiveness1 has a defect: if X.__init__ exists BUT fails (raising an AttributeError because of some internal logic problem, typo, etc), our __init__ will mask that! Version 2 is vastly superior in robustness, since it separates the sub-task of accessing the X.__init__ callable object (unbound method object) from that of calling it. Only the access to the callable object's reference is protected in the try/except, the call happens only when no exception was seen (that's what the "else" clause is for in try/except...!) and if executing the call internally raises any exceptions they will be correctly propagated.
Separating "obtain the callable" from "call it" leads us right into the "Let's Homogeinize Different Cases" approaches. Again, this is a general approach (in Python, and more generally in programming) that often leads to simpler, more linear code (and sometimes to better speed, too): instead of checking for some possible special-cases, we do some pre-processing that ensures we are in "regular" cases anyway, then we can proceed under full assumption of regularity. The "sentinel" idiom in searches is another good example of "LHDC" in a completely different context. The only difference between the last two examples is how the do-nothing callable is built -- one uses a simple nested function whose names makes its role (or non-role:-) totally obvious, the other one uses a lambda. Choice between them is stricly a style issue (personally, I prefer to name my functions, but some dyed-in-the-wool functional programmers swear by lambda... [most of us swear AT it, though:-)]).
Multiple inheritance. It may be that this "recipe" is long enough as is... I certainly like the listing of different alternative approaches with commentary. But you haven't even one example of doing this with multiple inheritance. I can generalize from what you HAVE shown fairly easily... all I need to know is whether it is straightforward or if there are little tricks I need to be wary of.
Not that I use multiple inheritance all that much... I never liked it much.
-- Michael Chermside Michael Chermside
Multiple Inheritance. Maybe the 'Getting a dictionary of all members of a class hierarchy'-recipe might point to a possible solution.
the EasierToAskForgiveness idioms are themselves too forgiving. if X.__init__ exists and raises AttributeError, you won't hear about it.