2

I have a structure like

class A:
    def __init__(self, x):
        self.a=x

class B(A):
    def __init__(self, x):
        self.b=x

class C(A):
    def __init__(self, x):
        self.c=x

class D(B,C):
    def __init__(self, x):
        self.d=x

Now I'd like to extend the __init__s such that both B and C will call A, too (B and C can be used as stand-alone classes). Moreover D should call A, B and C (D combines features from B and C but still needs to run all initializations). All __init__ take the same parameter. Obviously A should be called only once.

Do you see an easy way to do that?

Gere
  • 12,075
  • 18
  • 62
  • 94
  • 3
    Read this article on `super()`: http://rhettinger.wordpress.com/2011/05/26/super-considered-super/ . It deals precisely with how to solve this problem in a complex class hierarchy in Python. – millimoose Nov 08 '12 at 21:05
  • Also, your design here is off. You really shouldn't need the same argument in every constructor. – millimoose Nov 08 '12 at 21:07
  • 1
    Are you in Python 2 or 3? And, if 2, did you mean to write a classic rather than new-style class? – abarnert Nov 08 '12 at 21:14
  • Seeing as they're going away, you should never really mean to write a old-style class. – millimoose Nov 08 '12 at 21:16
  • @millimoose: They've already gone away in 3.0 and later, and are never going away in 2.x, so… if you want old-style behavior in 2.x, it's actually safe to use it. Still a bad idea, of course. – abarnert Nov 08 '12 at 21:19

2 Answers2

6

Use super. As far as I'm aware, this is it's purpose ...

First, some proposed reading:

  • Super considered harmful and super (simultaneously -- by different people)

Next, an example:

class A(object):
    def __init__(self, x):
        print "Called A"
        self.a=x

class B(A):
    def __init__(self, x):
        print "Called B"
        super(B,self).__init__(x)
        self.b=x

class C(A):
    def __init__(self, x):
        print "Called C"
        super(C,self).__init__(x)
        self.c=x

class D(B,C):
    def __init__(self, x):
        print "Called D"
        super(D,self).__init__(x)        
        self.d=x


foo = D(10)

As stated in the comments, you often see methods which use super defined to accept any number of positional and keyword arguments:

def mymethod(self,*args,**kwargs):
    super(thisclass,self).method(*args,**kwargs)
    ...

As that allows super to pass the necessary/unnecessary arguments on to other objects in the inheritance chain. Those methods can then decide which arguments they need and ignore the rest (passing them on to the next super of course)


Finally, to complete this discussion, we need to discuss python2.x vs. python3.x. In python2.x, all of your classes must be new style (they need to inherit from object eventually). In python3.x, this is automatic. And in python3.x, you can omit the arguments to super.

 super().__init__(*args,**kwargs)

However, I prefer the python2.x style as it works for both python2.x and python3.x and I'm always left wondering how super() (python3.x style) knows what object to pass to the underlying methods as self. It seems a lot more magical then even the python2.x version to me...

mgilson
  • 300,191
  • 65
  • 633
  • 696
  • To be fair, doing this with `super()` *correctly* is nontrivial and nonobvious. – millimoose Nov 08 '12 at 21:06
  • @millimoose -- Generally, I think that doing anything with `super` is non-trivial and non-obvious. I generally don't use it because I don't *need* it in my code. Can you check my posted code and make sure that I did this *correctly* -- Commenting or editing if I did it wrong? – mgilson Nov 08 '12 at 21:09
  • Personally, I really like `super`. This code is correct if the class hierarchy is to be considered private - that is, if you're not expecting API users to try to inject classes into the MRO using multiple inheritance. If they _are_ trying to do that, you need to accept and pass on (or swallow, where appropriate) `*args` and `**kwargs` at every point in the tree, since you don't know what class will be called next by `super`. – Benjamin Hodgson Nov 08 '12 at 21:13
  • @poorsod -- Yes, I understand that you need to use `*args` and `**kwargs` where appropriate, but OP specified that all of the various `__init__` took the same arguments. – mgilson Nov 08 '12 at 21:14
  • @mgilson When all the constructor signatures across the hierarchy match, this is a good solution. (It's just an impractical restriction for real code is all.) – millimoose Nov 08 '12 at 21:15
  • @mgilson OP didn't specify whether _users_ of the API might try to manipulate the MRO using multiple inheritance. New user-defined base classes might use different argument signatures, for which you need to be extra careful not to get `TypeError`s – Benjamin Hodgson Nov 08 '12 at 21:17
  • It's worth mentioning that in Python 3 you can just `super().__init__(x)` instead of `super(D, self).__init__(x)`. This makes it much less non-trivial and non-obvious (since the part people always remember wrong, or screw up through copy-paste, no longer exists). – abarnert Nov 08 '12 at 21:20
  • @poorsod I don't think it's generally possible to let new classes join the hierarchy and not follow some set of restrictions. This answer is valid for the OP's proposed hierarchy (hokey as it is); a full treatment is somewhat out of scope for a SO answer and best left to the two notorious "`super()` considered X" articles. – millimoose Nov 08 '12 at 21:21
  • @millimoose - agreed. For readers, the two articles millimoose is referring to are "[Python's Super Considered Harmful](https://fuhm.net/super-harmful/)" and "[Python’s super() considered super!](http://rhettinger.wordpress.com/2011/05/26/super-considered-super/)". They explain, in fantastic detail, the big gotcha about `super` (which is that the next method called might be one you've never heard of). – Benjamin Hodgson Nov 08 '12 at 21:26
  • @poorsod -- Thanks for the links. I just finished adding them to my post when I saw your comment. – mgilson Nov 08 '12 at 21:31
  • Actually in Python 2, one of the occasionally-useful reasons to use classic classes is that `super` works better in certain cases. (And the same code will of course create new-style classes in Python 3, and `super` will still work fine.) In particular, if you're writing mixins, you can drop a super-friendly classic class as a mixin into a non-super-friendly hierarchy, but not a new-style one. (However, I will gladly concede that the OP probably shouldn't be learning that kind of stuff right now… if you want to do stuff that requires hacks in 2.x but not 3.x, learn 3.x instead of the hacks…) – abarnert Nov 08 '12 at 21:36
  • Also, there's another advantage of Python 3 `super` besides not having the remember the arguments: DRY. If you rename the class, you don't need to edit all `super` calls anywhere in the class to work. (I've seen people do `super(self.__class__, self)` in 2.x as a workaround, but this has its own problems.) – abarnert Nov 08 '12 at 21:43
  • Aaahhh. Why is that if I remove the superclass A from C, then C.__init__ isn't called anymore? – Gere Nov 08 '12 at 23:21
  • @Gerenuk -- That changes `D.__mro__` (try printing it). It moves `A` before `C` in the `__mro__`. Since there is no `super` in `A`, it doesn't call `C.__init__` (since that is `A-super's` responsibility now). You can move `super` from `C` to `A` and it works again. To handle this in general, I suppose you could put super in all of them and in classes that inherit directly from `object`, you could put the `super` call in a `try`/`except TypeError` ... but I'm not sure if that's the best way... – mgilson Nov 08 '12 at 23:50
  • @mgilson: Now I have two branches and I want to execute them: left first top to bottom; right next top to bottom. I played with order of inheritance and super for quite a while to get that right. Am I right in the assumption that I require a common super class to make this work? Otherwise there were issues with **kwargs not passed around or otherwise passed to object() causing an error. – Gere Nov 09 '12 at 09:57
2

Using super

class D(B, C):
    def __init__(self, *args, **kwargs):
        super(D, self).__init__(*args, **kwargs)
        self.d = args[0]

Some explanation about super here and a related question

In Python 2 you should also inherit from object to use new style classes, and add super to your other classes too.

Community
  • 1
  • 1
Facundo Casco
  • 10,065
  • 8
  • 42
  • 63
  • If you're writing code for Python 3, why not use the simplified `super` instead of the Python 2 style? Just remove the params: `super().__init__(*args, **kwargs)`. – abarnert Nov 08 '12 at 21:17