Calling __init__ with multiple inheritance

A

Axel Straschil

Hello!

Im working with new (object) classes and normaly call init of ther
motherclass with callin super(...), workes fine.

No, I've got a case with multiple inherance and want to ask if this is
the right and common case to call init:

class Mother(object):
def __init__(self, param_mother): print 'Mother'
class Father(object):
def __init__(self, param_father): print 'Father'
class Child(Mother, Father):
def __init__(self, param_mother, param_father):
Mother.__init__(self, param_mother)
Father.__init__(self, param_mother)
child = Child(1, 2)

Thanks, AXEL.
 
P

Peter Otten

Axel said:
Im working with new (object) classes and normaly call init of ther
motherclass with callin super(...), workes fine.

No, I've got a case with multiple inherance and want to ask if this is
the right and common case to call init:

class Mother(object):
def __init__(self, param_mother): print 'Mother'
class Father(object):
def __init__(self, param_father): print 'Father'
class Child(Mother, Father):
def __init__(self, param_mother, param_father):
Mother.__init__(self, param_mother)
Father.__init__(self, param_mother)
child = Child(1, 2)

It looks correct -- at least it /looked/ correct before tried to quote it...

Here is an alternative approach that massages the initializer signatures a
bit to work with super() in a multiple-inheritance environment:

class Mother(object):
def __init__(self, p_mother, **more):
print "Mother", p_mother, more
super(Mother, self).__init__(p_mother=p_mother, **more)

class Father(object):
def __init__(self, p_father, **more):
print "Father", p_father, more
super(Father, self).__init__(p_father=p_father, **more)

class Child(Mother, Father):
def __init__(self, p_mother, p_father, **more):
print "Child", p_father, p_mother, more
super(Child, self).__init__(p_mother=p_mother, p_father=p_father,
**more)

Child(1, 2)

Unrecognized parameters are stashed away into the <more> dictionary.

Peter
 
G

Guest

What if I want to call other methods as well? Modifying your example a
bit, I'd like the reset() method call of Child to invoke both the
Mother and Father reset() methods, without referencing them by name,
i.e., Mother.reset(self).

-------------------
class Mother(object):
def __init__(self, p_mother, **more):
print "Mother", p_mother, more
super(Mother, self).__init__(p_mother=p_mother, **more)
def reset(self):
print 'resetting Mother'

class Father(object):
def __init__(self, p_father, **more):
print "Father", p_father, more
super(Father, self).__init__(p_father=p_father, **more)
def reset(self):
print 'resetting Father'

class Child(Mother, Father):
def __init__(self, p_mother, p_father, **more):
print "Child", p_mother, p_father, more
super(Child, self).__init__(p_mother=p_mother,
p_father=p_father, **more)
def reset(self):
print 'resetting Child'

# I would like to invoke both Mother.reset()
# and Father.reset() here, but it doesn't work.
print 'trying "super"'
super(Child, self).reset()

# These next two do work, but require referencing
# Mother and Father directly.
print "calling directly"
Mother.reset(self)
Father.reset(self)

a = Child(1, 2)
a.reset()
 
P

Peter Otten

What if I want to call other methods as well? Modifying your example a
bit, I'd like the reset() method call of Child to invoke both the
Mother and Father reset() methods, without referencing them by name,
i.e., Mother.reset(self).

[example snipped]

The problem with that aren't incompatible signatures, but the lack of an
implementation of the reset() method at the top of the diamond-shaped
inheritance graph that does _not_ call the superclass method. That method
could be basically a noop:


class Base(object):
def reset(self):
# object does something similar for __init__()
pass

class Mother(Base):
def reset(self):
print "resetting Mother"
super(Mother, self).reset()

class Father(Base):
def reset(self):
print "resetting Father"
super(Father, self).reset()

class Child(Mother, Father):
def reset(self):
print "resetting Child"
super(Child, self).reset()


Child().reset()

It might sometimes be convenient if such methods would magically spring into
existence for the object class, but for now you have to do it manually (or
perhaps someone has come up with a metaclass that I am not aware of).

Peter

PS: See http://www.python.org/2.2/descrintro.html for more on the subject.
 
G

Guest

Peter Otten wrote:

The problem with that aren't incompatible signatures, but the lack of an
implementation of the reset() method at the top of the diamond-shaped
inheritance graph that does _not_ call the superclass method. That method
could be basically a noop:

<snip>

Ok, now that's cool! Up until now I've been getting along just fine
without new-style classes, but that is about to change. Thanks for the
tip!

Phil
 
P

Peter Otten

jfj said:
Peter Otten wrote:



Is there any advantage using super in this case?
I think the case Father.__init__ (self, params) is simpler
and does the job perfectly well.

I agree.
super seems to be needed in "Dynamic Inheritance" cases where
we don't know an object's bases and there are comlicated mro issues!

Suppose you wanted factor out common code from the Father and Mother classes
into a Parent class -- something neither complicated nor farfetched. With
explicit calls to Parent.__init__() you would end up calling it twice from
Child.__init__(). So when you anticipate that your class hierarchy may
change, or that your classes may be subclassed by users of your library, I
think super() is somewhat less errorprone.

Peter
 
M

Michael Spencer

What if I want to call other methods as well? Modifying your example a
bit, I'd like the reset() method call of Child to invoke both the
Mother and Father reset() methods, without referencing them by name,
i.e., Mother.reset(self).

-------------------
class Mother(object):
def __init__(self, p_mother, **more):
print "Mother", p_mother, more
super(Mother, self).__init__(p_mother=p_mother, **more)
def reset(self):
print 'resetting Mother'

class Father(object):
def __init__(self, p_father, **more):
print "Father", p_father, more
super(Father, self).__init__(p_father=p_father, **more)
def reset(self):
print 'resetting Father'

class Child(Mother, Father):
def __init__(self, p_mother, p_father, **more):
print "Child", p_mother, p_father, more
super(Child, self).__init__(p_mother=p_mother,
p_father=p_father, **more)
def reset(self):
print 'resetting Child'

# I would like to invoke both Mother.reset()
# and Father.reset() here, but it doesn't work.
print 'trying "super"'
super(Child, self).reset()

# These next two do work, but require referencing
# Mother and Father directly.
print "calling directly"
Mother.reset(self)
Father.reset(self)

a = Child(1, 2)
a.reset()
if you're going to be doing a bunch of these, you could define your own variant
of the super type that calls all the methods in the mro with the given name.
Below is a minor modification to the pure-python version of built-in super that
illustrates this. There is no error-checking (e.g., if an attribute is callable
in one class, and not callable in another you will hit problems) and it's not
tested beyond what you see:

"""Hack on Super defined in http://python.org/2.2/descrintro.html
to call all super-class methods"""

class PolySuper(object):
def __init__(self, type, obj=None):
self.__type__ = type
self.__obj__ = obj
def __get__(self, obj, type=None):
if self.__obj__ is None and obj is not None:
return Super(self.__type__, obj)
else:
return self
def __getattr__(self, attr):
if isinstance(self.__obj__, self.__type__):
starttype = self.__obj__.__class__
else:
starttype = self.__obj__
mro = iter(starttype.__mro__)
for cls in mro:
if cls is self.__type__:
break
# Note: mro is an iterator, so the second loop
# picks up where the first one left off!
for cls in mro:
if attr in cls.__dict__:
x = cls.__dict__[attr]
if hasattr(x, "__get__"):
x = x.__get__(self.__obj__)

# return x
# We want all the resolved methods, in order
yield x
#raise AttributeError, attr

# Add this crude callable interface
def __call__(self, attr, *args, **kw):
methods = list(self.__getattr__(attr))
return [method(*args, **kw) for method in methods]


Then, your classes get defined like so:

class Mother(object):
def __init__(self, p_mother, **more):
print "Mother", p_mother, more
def reset(self):
print 'resetting Mother'

class Father(object):
def __init__(self, p_father, **more):
print "Father", p_father, more
def reset(self):
print 'resetting Father'

class Child(Mother, Father):
def __init__(self, p_mother, p_father, **more):
print "Child", p_mother, p_father, more
PolySuper(Child, self)("__init__", p_mother = p_mother, p_father =
p_father)
def reset(self):
print 'resetting Child'
PolySuper(Child, self)("reset")
Child M1 F1 {}
Mother M1 {'p_father': 'F1'}
Father F1 {'p_mother': 'M1'} resetting Child
resetting Mother
resetting Father
HTH
Michael
 
P

Peter Otten

jfj said:
As for the case where the users of the library want to subclass, I don't
see a problem.  They know they must subclass from class XXX and so they
call XXX.__init__ to construct it.

I was thinking of

class Child(Father, Mother):
pass

where Father and Mother have a common base class Parent. That would be
initialized twice:
.... def __init__(self):
.... print "parent"
........ def __init__(self):
.... print "father"
.... Parent.__init__(self)
........ def __init__(self):
.... print "mother"
.... Parent.__init__(self)
........ def __init__(self):
.... print "child"
.... Father.__init__(self)
.... Mother.__init__(self)
....
father
parent

mother
parent
child
father
parent
mother
parent # <-- the culprit: parent initialized a second time
<__main__.Child object at 0x402ad66c>

You have several options now:
- Write __init__() in such a way that calling it twice does no harm
- Ensure that it is only called once by adding a self.initialized flag
- Document that a user class may not inherit from both Father and Mother
but the best is IMO
- let super() do the work
In the case of Parent diamond inheritance, super() can avoid calling
the __init__ of parent twice?  How?

That's the best part -- it's automatic:
.... def __init__(self):
.... print "parent"
.... super(Parent, self).__init__()
........ def __init__(self):
.... print "father"
.... super(Father, self).__init__()
........ def __init__(self):
.... print "mother"
.... super(Mother, self).__init__()
........ def __init__(self):
.... print "child"
.... super(Child, self).__init__()
....
father
parent

mother
parent
child
father
mother
parent # <-- parent only once
<__main__.Child object at 0x402ad38c>

That was a lot of dull code -- but you asked :)
Seriously, I think super() scales better in a scenario with multiple
inheritance, though I won't start a holy war about the issue.

Peter
 
J

jfj

Peter Otten wrote:

Here is an alternative approach that massages the initializer signatures a
bit to work with super() in a multiple-inheritance environment:
super(Father, self).__init__(p_father=p_father, **more)


Is there any advantage using super in this case?
I think the case Father.__init__ (self, params) is simpler
and does the job perfectly well.

super seems to be needed in "Dynamic Inheritance" cases where
we don't know an object's bases and there are comlicated mro issues!


jfj
 
J

jfj

Peter said:

Ok. thanks for the confirmation.
Suppose you wanted factor out common code from the Father and Mother classes
into a Parent class -- something neither complicated nor farfetched. With
explicit calls to Parent.__init__() you would end up calling it twice from
Child.__init__(). So when you anticipate that your class hierarchy may
change, or that your classes may be subclassed by users of your library, I
think super() is somewhat less errorprone.

I accept the case that you avoid bugs if you extend the hierarchy
upwards. Although that's rare.

As for the case where the users of the library want to subclass, I don't
see a problem. They know they must subclass from class XXX and so they
call XXX.__init__ to construct it.

In the case of Parent diamond inheritance, super() can avoid calling
the __init__ of parent twice? How?


jfj
 
A

Axel Straschil

Hello!

Thanks to all for the very interesting postings!

I came to the following:

For single inheritance, super is a nice tool if you will recfactoring
the class later.

For multiple inheritance, if you want to use super, you have to have
very much knowledge of the classes you inheritance. For me, OOP is to
not have to have the deep inner knowledge of the classes I inheritance
from.

Also, for multiple inheritance, if think
Mother1.__init__(self, ...)
Mother2.__init__(self, ...)
Mother3.__init__(self, ...)
would be more clear to read then
super(MyClass).__init__(Maby this will do some magic thing)

Also, I realy dislike
__init__(self, param, **eat_some_needless_stuff)

If I later extend my class and also add some parameters to __init__,
what will happen to the classes using the baseclass?

Also, lool at that:
class Mother(object):
def __init__(self, param_mother='optional', **eat):
print 'Mother'
class Father(object):
def __init__(self, param_father='optional', **eat):
print 'Father'
class Child(Mother, Father):
def __init__(self, **ham):
super(Child, self).__init__(**ham)
child = Child(param_mother=1, param_father=1)

Father's init will not be called.

Thanks,
AXEL.
 
S

Steven Bethard

Axel said:
I came to the following:

For single inheritance, super is a nice tool if you will recfactoring
the class later.

For multiple inheritance, if you want to use super, you have to have
very much knowledge of the classes you inheritance.

And for multiple inheritance, if you don't use super, you're likely to
call the same method twice if you ever have a diamond inheritance problem.

STeVe
 
P

Peter Otten

Axel said:
Thanks to all for the very interesting postings!

You're welcome.
I came to the following:

For single inheritance, super is a nice tool if you will recfactoring
the class later.

Or if you start out with a diamond inheritance graph from the beginning.
For multiple inheritance, if you want to use super, you have to have
very much knowledge of the classes you inheritance. For me, OOP is to
not have to have the deep inner knowledge of the classes I inheritance
from.

I failed to bring my point that you need _less_ knowledge across then.
Also, for multiple inheritance, if think
Mother1.__init__(self, ...)
Mother2.__init__(self, ...)
Mother3.__init__(self, ...)
would be more clear to read then
super(MyClass).__init__(Maby this will do some magic thing)

For an advantage to show up you need at least three levels of inheritance,
see my example with the Parent class.
Also, I realy dislike
__init__(self, param, **eat_some_needless_stuff)

In turn, I really like the name you gave the dictionary parameter :)
Normalize the initializer signature to a common set of arguments. Or pass a
single parameter and take the actual information from its attributes. Or
write a custom dispatcher, that passes only parameters that correspond to
formal arguments...
If I later extend my class and also add some parameters to __init__,
what will happen to the classes using the baseclass?

Also, lool at that:
class Mother(object):
def __init__(self, param_mother='optional', **eat):
print 'Mother'
class Father(object):
def __init__(self, param_father='optional', **eat):
print 'Father'
class Child(Mother, Father):
def __init__(self, **ham):
super(Child, self).__init__(**ham)
child = Child(param_mother=1, param_father=1)

Father's init will not be called.

Change Father/Mother.__init__() to call the superclass initializer. It may
be counterintuitive, but it works.

Peter
 
A

Axel Straschil

Hello!
Change Father/Mother.__init__() to call the superclass initializer. It may
be counterintuitive, but it works.

OK, thanks, with the super(...).__init__() in Father/Mother it workes
and makes sense.

So, the last thing a *realy* don't like ist the
__init__(self, param, **ignore_the_rest) thing.

Anyone had troubles with that, or should I cust take this as a "python
way of thinking" ... ;-), and getting used to that?

Thanks,
AXEL.
 
J

jfj

Peter said:
child
father
mother
parent # <-- parent only once
<__main__.Child object at 0x402ad38c>

D-uh?

################################################
class Parent(object):
def __init__(self):
print "parent"
super(Parent, self).__init__()

class Father(Parent):
def __init__(self):
print "father"
super(Father, self).__init__()
print "D-uh"

class Mother(Parent):
def __init__(self):
print "mother"
super(Mother, self).__init__()
print "D-oh"

class Child(Father, Mother):
def __init__(self):
print "child"
super(Child, self).__init__()

Child()
################################################

This prints
child
father
mother
parent
D-oh
D-uh

Therefore super is a very intelligent function indeed!


jf
 

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,774
Messages
2,569,596
Members
45,143
Latest member
DewittMill
Top