Dispatch with multiple inheritance

  • Thread starter Michael J. Fromberger
  • Start date
M

Michael J. Fromberger

Consider the following class hierarchy in Python:

class A (object):
def __init__(self):
print "cons A"

class B (object):
def __init__(self):
print "cons B"

class C (A):
def __init__(self):
super(C, self).__init__()
print "cons C"

class D (B):
def __init__(self):
super(D, self).__init__()
print "cons D"

Now, suppose I would like to define a new class as follows:

class E (C, D):
...

In the constructor for E, I would like to invoke the constructors of
both parent classes. The correct way to do this seems to be exemplified
by the following:

class E (C, D):
def __init__(self):
super(E, self).__init__() # calls C constructor
super(A, self).__init__() # calls D constructor (**)
print "cons E"

This works, but I find it somewhat troubling. It seems to me that one
should not have to "know" (i.e., write down the names of) the ancestors
of C in order to dispatch to superclass methods in D, since C and D
share no common ancestors south of object.

Is there a better (i.e., more elegant) way to handle the case marked
(**) above?

Curious,
-M
 
N

Nick Vatamaniuc

Michael,
You only need to call the __init__ method of the superclass if you need
to do something special during initialization. In general I just use
the SuperClass.__init__(self,...) way of calling the super class
constructors. This way, I only initialize the immediate parents and
they will in turn call the init method of their parents and so on.
super() is probably the a better way to do things but I find
SuperClass.__init__(self, ...) more clear. For example your code would
be:
-------------------------------
class A (object):
def __init__(self):
print "cons A"

class B (object):
def __init__(self):
print "cons B"

class C (A):
def __init__(self):
A.__init__(self)
print "cons C"

class D (B):
def __init__(self):
B.__init__(self)
print "cons D"

class E(D,C):
def __init__(self):
C.__init__(self)
D.__init__(self)
print "cons E"

e=E()
----------------------------------
Output should be:
cons A
cons C
cons B
cons D
cons E

In general note that __init__ is NOT a constuctor it is an initializer
(therefore the name __init__ as opposed to __constr__ or __new__). The
object is constructed by Python already and is given to init method as
the 'self' argument. The construction can also be customized inside the
__new__ magic method, but normally you don't even need to know about
__new__.

Hope this helps,
Nick V.
 
L

looping

Michael said:
Is there a better (i.e., more elegant) way to handle the case marked
(**) above?

You have to call super in each method __init__, if you don't, the call
chain break before the end:

class A (object):
def __init__(self):
super(A, self).__init__()
print "cons A"

class B (object):
def __init__(self):
super(B, self).__init__()
print "cons B"

class C (A):
def __init__(self):
super(C, self).__init__()
print "cons C"

class D (B):
def __init__(self):
super(D, self).__init__()
print "cons D"

class E (C, D):
def __init__(self):
super(E, self).__init__() # calls C constructor
print "cons E"
 
L

looping

looping said:
You have to call super in each method __init__, if you don't, the call
chain break before the end:

class A (object):
def __init__(self):
super(A, self).__init__()
print "cons A"

class B (object):
def __init__(self):
super(B, self).__init__()
print "cons B"

class C (A):
def __init__(self):
super(C, self).__init__()
print "cons C"

class D (B):
def __init__(self):
super(D, self).__init__()
print "cons D"

class E (C, D):
def __init__(self):
super(E, self).__init__() # calls C constructor
print "cons E"

After a second tought, it's probably better to call __init__ method
explicitly in class E:

class A (object):
def __init__(self):
print "cons A"

class B (object):
def __init__(self):
print "cons B"

class C (A):
def __init__(self):
super(C, self).__init__()
print "cons C"

class D (B):
def __init__(self):
super(D, self).__init__()
print "cons D"

class E (C, D):
def __init__(self):
D.__init__(self)
C.__init__(self)
print "cons E"

this way you have to choose which __init__ from class D or class C is
calling, and which is calling first.
Any Python Guru to give is opinion ?
 
M

Michael J. Fromberger

"Michele Simionato said:
Michael J. Fromberger ha scritto:



Look at
http://www.python.org/download/releases/2.3/mro

Hi, Michele,

I understand what Python's method resolution order is, and how it is
constructed. Is there something else in that article, that you hoped
would address my original concern? In particular, I am not asking WHY
super(A, self).__init()__ yields the next method in order -- that is
quite clear. Rather, I want to know if there is a better way to invoke
the ancestry of class E from D, than to explicitly denote the ancestry
of C in the code. The latter is an ugly violation of abstraction, and
renders the class hierarchy brittle.

Of course, I could just bypass super, and explicitly invoke them as:

C.__init__(self, ...)
D.__init__(self, ...)

.... but that seems to me to defeat the purpose of having super in the
first place.

It seems as if perhaps there is no better solution at present. That is
a pity, albeit a mild one. Thank you for taking the time to respond.

-M
 
M

Michael J. Fromberger

"Nick Vatamaniuc said:
Michael,
You only need to call the __init__ method of the superclass if you need
to do something special during initialization.

Hi, Nick,

Thank you for responding. I understand the purpose in invoking the
superclasses' __init__ methods. Let us take it as a given that I
require this behaviour; the simple example does not show it, but I am
using the same technique in a much more elaborate program, where in fact
the superclass initialization is required.
In general I just use the SuperClass.__init__(self,...) way of
calling the super class constructors.

Yes, and that certainly works just fine. But it obviates the point of
having super(). But perhaps that is the take-home message.

I do not want A's constructor to dispatch further along in the MRO for E
for two reasons: One, because the constructors along the (E D B) chain
take different argument lists (in my real code) than the (E C A) path;
and two, because I happen to care about the order in which the methods
are invoked.
In general note that __init__ is NOT a constuctor it is an initializer

Yes, you're right; I apologize for the imprecision. However, for the
purposes of this example, the distinction is irrelevant.

Cheers,
-M
 
M

Michael Spencer

Michael J. Fromberger wrote:
....
Of course, I could just bypass super, and explicitly invoke them as:

C.__init__(self, ...)
D.__init__(self, ...)

... but that seems to me to defeat the purpose of having super in the
first place.

As others have pointed out, super, is designed to do something different from
what you want. See
http://www.python.org/download/releases/2.2.3/descrintro/#cooperation for GvR's
explanation of super's intent, limitations, and a pure-python implementation
that you might alter for your purposes.

Some people argue that effective use cases for super are rare and that it's
awkward to use correctly even in these cases. See for example,
http://fuhm.net/super-harmful/. So don't feel bad about not using it ;-)

Given what you assert about your inheritance graph (two independent ancestor
chains, that do not propagate super calls from their respective base classes),
what is objectionable about:
> C.__init__(self, ...)
> D.__init__(self, ...)

Michael
 
M

Michael J. Fromberger

Michael Spencer said:
As others have pointed out, super, is designed to do something different from
what you want. See
http://www.python.org/download/releases/2.2.3/descrintro/#cooperation for
GvR's
explanation of super's intent, limitations, and a pure-python
implementation
that you might alter for your purposes.

Indeed so. The documentation for super() led me to an erroneous
conclusion.
Given what you assert about your inheritance graph (two independent ancestor
chains, that do not propagate super calls from their respective base
classes),
what is objectionable about:

Well, since super() were much better labelled call_next_method, I must
agree that the Class.__init__(self, ...) form is probably the best
solution!

Thanks for your feedback.

Cheers,
-M
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top