2

I cannot figure out why in the following code only one parent constructor is initialized:

class Base(object):
    def __init__(self):
        print "Base::Base():"


class BaseWArgs(object):
    def __init__(self, arg):
        print "BaseWArgs::BaseWArgs(%s):" % arg


class Composite(Base, BaseWArgs):
    def __init__(self):
        super(Composite,self).__init__()

In this case, only Base.init() is called. If I switch the order of inheritance parameters, only the whatever-stands-first class is initialized:

class Composite(BaseWArgs, Base):
    def __init__(self):
        super(Composite,self).__init__('myArg')

How I can initialize both parents with one call? Shouldn't that be the responsibility of super()?

EDIT I think my use case is a special case of multiple inheritance. I want to create an item using defaultdict as a base type, but inheriting some additional funcitonality from a third type.

from collections import defaultdict

class Base(object):
    def __init__(self):
        print "Base::Base():"
        super(Base,self).__init__()
        self.val = 'hello world'

class Item(defaultdict, Base):
    def __init__(self):
        super(Item,self).__init__(int)

>>> Item()
defaultdict(<type 'int'>, {})

>>> Item().val
Traceback (most recent call last):
  File "<pyshell#77>", line 1, in <module>
    Item().val
AttributeError: 'Item' object has no attribute 'val'

Where does Base::Base() get lost? Why is Base not initialized?

Pavlo Dyban
  • 1,297
  • 2
  • 15
  • 28
  • 1
    Before anyone starts linking to that "super is harmful" stuff: [Python’s super() considered super!](http://rhettinger.wordpress.com/2011/05/26/super-considered-super/) –  Nov 18 '13 at 12:46
  • You have to use `super` twice. `super` simply walks the `__mro__` (method resolution order) in order to find the method to call, but it *always* finds *one* method, i.e. the first one in the hierarchy. – Bakuriu Nov 18 '13 at 12:46
  • @Bakuriu Using `super()` twice calls the same `Base.__init__()` twice. – Pavlo Dyban Nov 18 '13 at 12:48
  • You should read about new python classes and inheritence, that's because python don't search all parents it's take first near. – Denis Nov 18 '13 at 12:49
  • @PavloDyban Read my comment. Did I ever said to call `super` twice *with the same arguments*? I **obviously** meant to call `super` twice, with *different* arguments in order to obtain the two methods. – Bakuriu Nov 18 '13 at 12:50
  • @Bakuriu: `super` is meant to be called with the class and instance. I don't see what "other argument" you'd want to pass in a second call in `Composite` (and _please_ don't answer `(Base, self)` or `(BaseWArgs, self)`) – bruno desthuilliers Nov 18 '13 at 12:56
  • @brunodesthuilliers No, `super` is meant to be called with *a* class and an instance of that class, which need not be a "direct instance". Also, you can access the `__mro__` of a class in order to get the name of the parent classes (with an arbitrary complex algorithm) without explicitly mentioning them in the code. – Bakuriu Nov 18 '13 at 13:04
  • @Bakuriu: ok the you meant manually doing some of the black magic `super` is supposed to avoid. In that case I'd rather go for an explicit call to the "uncollaborative" parent's method. – bruno desthuilliers Nov 18 '13 at 13:20
  • `__mro__` is an important attribute which I was not aware of when implementing complicated multiple inheritance. Thanks for pointing that out to me! – Pavlo Dyban Nov 18 '13 at 13:32

1 Answers1

3

Neither Base.__init__ nor BaseWArgs.__init__ implement the call to super().__init__, so obviously only the first one in the rmo will get called. You have to have all initializer respect the protocol for it to work.

Also, with Base coming first in the mro and not taking any argument, this will still fail. "collaborative" super calls require that your methods signatures are compatible. The below code do delegate the call as expected:

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

class BaseWArgs(object):
    def __init__(self, arg):
        print "BaseWArgs::BaseWArgs(%s):" % arg
        super(BaseWArgs, self).__init__()


class Composite(BaseWArgs, Base):
    def __init__(self):
        super(Composite,self).__init__("foo")

c = Composite()

edit : wrt/ your concrete use case, it seems that defaultdict does not do the super call itself so unless it's the last in your base classes the call chain will stop there (well, it will stop there, but that's not a problem if it comes last ).

So you choices are either to make your whole own Base hierachy's initializers compatible with defaultdict's one and have defaultdict as the last base class, or explicitely call defaultdict's initializer then Base initializer, ie:

class Base(object):
    def __init__(self):
        print "Base__init__():"
        super(Base, self).__init__()

class Sub(Base):
    def __init__(self):
        print "Sub.__init__():"
        super(Sub, self).__init__()
        self.foo = "bar"


class MyClass(defaultdict, Sub):
    def __init__(self):
        defaultdict.__init__(self, int)
        Sub.__init__(self)
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • That explains why switching places between `defaultdict` & my own Base class resolved inheritance. That's because `defaultdict` does not inherit from `object` & does not implement `super().__init__()`. – Pavlo Dyban Nov 18 '13 at 12:53
  • @PavloDyban `defaultdict` **does** inherit from `object`, although not *directly*. – Bakuriu Nov 18 '13 at 12:58
  • @PavloDyban : sorry sir but `defaultdict` does inherits from `object` - now I don't know whether it calls on `super` or not. – bruno desthuilliers Nov 18 '13 at 12:58
  • Then I am stuck again. – Pavlo Dyban Nov 18 '13 at 12:59
  • @PavloDyban: what's your _real_ use case ? – bruno desthuilliers Nov 18 '13 at 13:01
  • @ndpu: implementing the call to super in Base and BaseWArgs will make sure each class in the mro do call the next one. This won't solve potential problems with incompatible signatures. – bruno desthuilliers Nov 18 '13 at 13:12
  • @brunodesthuilliers But not so if you derive `BaseWArgs` from e.g. `defaultdict`. `def __init__(self): super(BaseWArgs,self).__init__(int)`. Now if that class stands first in `Composite`'s order of inheritance, `Base` will not be initialized. – Pavlo Dyban Nov 18 '13 at 13:39
  • @PavloDyban: yes, that's because `defaultdict` will then come before `Base` in the mro and since obviously defaultdict doesn't do the `super` call the call chain stops there. – bruno desthuilliers Nov 18 '13 at 14:29