Great post Paul !
On Thu, Dec 23, 2010 at 7:00 PM, Paul Molodowitch <[email protected]>wrote: > 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 > -- David Moulder http://www.google.com/profiles/squish3d -- http://groups.google.com/group/python_inside_maya
