17

So, i have a situation like this.

class A(object):
    def foo(self, call_from):
        print "foo from A, call from %s" % call_from


class B(object):
    def foo(self, call_from):
        print "foo from B, call from %s" % call_from


class C(object):
    def foo(self, call_from):
        print "foo from C, call from %s" % call_from


class D(A, B, C):
    def foo(self):
        print "foo from D"
        super(D, self).foo("D")

d = D()
d.foo()

The result of the code is

foo from D
foo from A, call from D

I want to call all parent method, in this case, foo method, from D class without using super at the parent class like A. I just want to call the super from the D class. The A, B, and C class is just like mixin class and I want to call all foo method from D. How can i achieve this?

thefourtheye
  • 233,700
  • 52
  • 457
  • 497
Edwin Lunando
  • 2,726
  • 3
  • 24
  • 33

3 Answers3

13

You can use __bases__ like this

class D(A, B, C):
    def foo(self):
        print("foo from D")
        for cls in D.__bases__:
            cls().foo("D")

With this change, the output will be

foo from D
foo from A, call from D
foo from B, call from D
foo from C, call from D
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
thefourtheye
  • 233,700
  • 52
  • 457
  • 497
  • This is a little sketchy ... What if you now have two classes which both inherit from D and then another class which inherits from both of those? Now `foo` will call `A.foo` twice. – mgilson Dec 08 '13 at 07:49
  • 2
    Or what happens if `B.foo` doesn't exist? And finally, I think it should be `cls.foo(self)`, not `cls().foo()` – mgilson Dec 08 '13 at 07:50
  • @mgilson Then, we have to use `mro`? And those are not class methods, right? So, I had to create objects to call them. – thefourtheye Dec 08 '13 at 07:51
  • 1
    They're not class methods, but you can call the method from the class if you explicitly pass `self` as the first argument (and `self` is of the correct type). – mgilson Dec 08 '13 at 07:56
  • In this case, we don't have a problem with `self`, but in his actual code, he might mutate `self` and that would create problems. So, it would be better to call it this way, I believe – thefourtheye Dec 08 '13 at 08:01
  • This is what I need! Thank you. :D. I changed the `D__bases__` into `self.__class__.__bases__` and it work greatly. – Edwin Lunando Dec 08 '13 at 09:56
11

Add super() call's in other classes as well except C. Since D's MRO is

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

You don't need super call in C.

Code:

class A(object):
    def foo(self, call_from):
        print "foo from A, call from %s" % call_from
        super(A,self).foo('A')

class B(object):
    def foo(self, call_from):
        print "foo from B, call from %s" % call_from
        super(B, self).foo('B')


class C(object):
    def foo(self, call_from):
        print "foo from C, call from %s" % call_from

class D(A, B, C):
    def foo(self):
        print "foo from D"
        super(D, self).foo("D")

d = D()
d.foo()

Output:

foo from D
foo from A, call from D
foo from B, call from A
foo from C, call from B
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
  • In case you do not understand my description, I want to call all `foo` method without calling super at the parent class like `A` or `B` or `C`. – Edwin Lunando Dec 08 '13 at 07:41
  • 1
    @EdwinLunando -- I believe that's just not how it goes. In order for super to work properly, all the classes need to use it. [see the section on mixing super methods with non super methods](https://fuhm.net/super-harmful/). – mgilson Dec 08 '13 at 07:47
  • @EdwinLunando Then iterate over `__mro__`, or `__bases__` and call each class explicitly, but then you **can't use** `super` in `D`. – Ashwini Chaudhary Dec 08 '13 at 07:48
  • 2
    This solution is wrong because now calling `A().foo()` or `B().foo()` will raise an exception. You can now only use these classes from a derived class which also derives from `C` (or a similar class), and the order becomes important as well (`C` must be last). To make this work correctly, you'd need all classes to be derived from a base class that implements `foo`. – interjay May 13 '14 at 13:35
1

I believe the call to super in sub-classes is the more Pythonic approach. One doesn't have to use the parent class names (particularly in super). Elaborating on the previous example, here's some code that should work (python 3.6+):

class A:
    def foo(self, call_from):
        print(f"foo from A, call from {call_from}")
        super().foo('A')

class B:
    def foo(self, call_from):
        print(f"foo from B, call from {call_from}")
        super().foo('B')


class C(object):
    def foo(self, call_from):
        print(f"foo from C, call from {call_from}")
        super().foo('C')

class StopFoo:
    def foo(self, call_from):
        pass

class D(A, B, C, StopFoo):
    def foo(self, call_from):
        print(f"foo from D, call from {call_from}")
        super().foo('D')

If you run this code:

d = D()
d.foo('D')

You will get:

foo from D, call from D
foo from A, call from D
foo from B, call from A
foo from C, call from B

The advantage of this strategy is you don't have to bother about the inheritance order, AS LONG as you include the StopFoo class. This one is a bit peculiar and might not be the best strategy to accomplish this task. Basically, every class in the inheritance tree calls the foo method and calls the parent method, which does the same. We might be talking about multiple inheritance, but an inheritance tree is actually flat (D -> A -> B -> C -> Stopfoo -> object). We can change the order of inheritance, add new classes to this pattern, remove them, just call one class... but the trick remains: include StopFoo before the call to foo leaves the class we have defined.

For a mixin pattern with hooks, this might make sense. But of course, the solution isn't that useful in every case either. Don't fear super though, it has many tricks and can be useful in simple and multiple inheritance, with mixins or simple abstract classes.

vincent-lg
  • 539
  • 7
  • 15