The way super is intended to be used is as a way to provide a single linear
chain of super calls which traverses through the inheritance hierarchy.

The order of the single chain it uses is the same one used for method
resolution order - so to see it, use inspect.getmro.  In your case, calling

import inspect
inspect.getmro(C)

gives:

(<class '__main__.C'>,
 <class '__main__.A'>,
 <class '__main__.B'>,
 <type 'object'>)


(If you want to know the nitty-gritty details of how python goes about
determining the mro, look here:
http://www.python.org/download/releases/2.3/mro/ )

The super call:

super(Foo, Bar)

can then be thought of conceptually as 'using the mro of Bar, find the next
object up the chain from Foo'.  Which explains why calling super(A, self) in
your case called the init of B: self was an instance of C, and the mro for C
is (C,A,B,object) - and, using that mro, the next class up from A is B.

Unfortunately, your solution is not a general one, because you're building
in an assumption about the mro into C.__init__ - namely, that the next
object in the inheritance chain after C will always be A - and this is not
always the case.  Consider this bit of code:

from pprint import pprint
import inspect

class A(object):
    def __init__(self):
        print 'A instantiated'
        self.important = 'a'

class B(object):
    def __init__(self):
        print 'B instantiated'
        self.important = 'b'
        self.bIsImportant = 'too'


class C(A, B):
    def __init__(self):
        print 'C instantiated'
        super(C, self).__init__()
        super(A, self).__init__()

class D(A, B):
    def __init__(self):
        print 'D instantiated'
        super(D, self).__init__()
        super(A, self).__init__()

class E(C, D):
    def __init__(self):
        print 'E instantiated'
        super(E, self).__init__()

pprint(inspect.getmro(E))
print
E()


The classes A,B,C are all identical to yours, except I've removed the
unnecessary pass statements, and added an info print to C.__init__.  With
class D, I've followed your example of A for inheriting from (A,B).  Then E
inherits from C,D.

When you run it, we get this:

mro C:
(<class '__main__.C'>,
 <class '__main__.A'>,
 <class '__main__.B'>,
 <type 'object'>)
making C:
C instantiated
A instantiated
B instantiated

mro D:
(<class '__main__.D'>,
 <class '__main__.A'>,
 <class '__main__.B'>,
 <type 'object'>)
making D:
D instantiated
A instantiated
B instantiated

mro E:
(<class '__main__.E'>,
 <class '__main__.C'>,
 <class '__main__.D'>,
 <class '__main__.A'>,
 <class '__main__.B'>,
 <type 'object'>)
making E:
E instantiated
C instantiated
D instantiated
A instantiated
B instantiated
B instantiated



C and D seem to instantiate correctly.. but when you try to create E, you
hit problems.  B is instantiated twice.  Why? Well, the  assumption we were
making when we called super(C,self) then super(A,self) was that
super(C,self) would yield A... then super(A,self) would give the next item
in the chain after A.  But the mro for E is (E,C,D,A,B) - so the assumption
that super(C,self) is A is now invalid.

The correct way to hit every element in the inheritance chain when using
super is to have every element in the super chain call super - even the ones
that inherit from object... because, even though by themselves their super
will simply be object, and the super call will be pointless, with multiple
inheritance, the super may NOT be object, but some other class in the mro
chain.

ie, if we change the definitions of A, B, C, and D to call their super once
and exactly once:

class A(object):
    def __init__(self):
        print 'A instantiated'
        self.important = 'a'
        super(A, self).__init__()

class B(object):
    def __init__(self):
        print 'B instantiated'
        self.important = 'b'
        self.bIsImportant = 'too'
        super(B, self).__init__()

class C(A, B):
    def __init__(self):
        print 'C instantiated'
        super(C, self).__init__()

class D(A, B):
    def __init__(self):
        print 'D instantiated'
        super(D, self).__init__()



...then everything works.  The reason it works is that super (ie, the mro)
provides a single, linear chain through the inheritance heirarchy... so if
every method calls it's super, we will hit every class in the hierarchy
once, and only once.

Unfortunately, we still have potential problems - ie, what if we want our
__init__ to take arguments, and not all the class's inits take the same
number of arguments?  This is explained in far more detail here:

http://fuhm.net/super-harmful/

The basic skinny is that, when using super + multiple inheritance, it's best
to use *args/**kwargs in ALL the methods that are using super.  Even then
though, you may run into problems - for instance, the example he gives is
when trying to use super and __new__... the problem being that
object.__new__ never accepts any args/kwargs.

Really, what I tend to take out of all this is:

Multiple Inheritance is very tricky
Use it only when necessary
When you do use it, try to make your inheritance schemes as as simple as
possible, and be very, very careful. ;)

- Paul

On Wed, Dec 22, 2010 at 11:56 AM, Viktoras <[email protected]> wrote:

> class C(A, B):
>
>>        def __init__(self):
>>                super(C, self).__init__()
>>                super(A, self).__init__()
>>                pass
>>
>
> unless you have a reason not to hardcode parent classes in the constructor,
> i think all you need is just not using super() at all:
>
> def __init__(self):
>        A.__init__(self)
>        B.__init__(self)
>        pass
>
>
>
>
> --
> http://groups.google.com/group/python_inside_maya
>

-- 
http://groups.google.com/group/python_inside_maya

Reply via email to