0

I came across the following constructor recently, but don't quite understand what a bunch of it is referencing:

class EobiExchange(L3Exchange):
    def __init__(self, *args, **kwargs):
        self.reference_template_data = None
        super().__init__(*args, **kwargs)
  • I do understand that super().__init__() means that it is inheriting from its Parent's Constructor? - please correct me if I am wrong! (i.e. L3Exchange's constructor in this case)
  • But what I completely don't understand is what the *args, **kwargs mean; I understand them in the general terms like it means you can pass in any number of arguments? But in this context I don't quite see it. Any example would be so helpful. Thanks
  • Lastly, if we have class EobiExchange(), but still used super(), what would the EobiExchange's constructor be referencing in this case? Is it other classes that have been defined within the same file but further up to this class?
Filip Müller
  • 1,096
  • 5
  • 18
Patrick_Chong
  • 434
  • 2
  • 12
  • *" like it means you can pass in any number of arguments?"*: it is no different for constructors. You may call that constructor with any arguments, and that code is telling the constructor to pass *exactly the same* arguments to the parent class's constructor. – trincot Jul 26 '22 at 10:10
  • Thanks trincot- this is what I thought but thought I'd check. And any comments on the last point above? – Patrick_Chong Jul 26 '22 at 10:12
  • `super().` calls the parent (or "super") class' method. `*args, **kwargs` is sometimes used a way of avoiding having to write out all the args from the parent class - the sub-class will accept any and all args passed to it and then pass them to the super... if it's the wrong args the parent will raise an exception. – Anentropic Jul 26 '22 at 10:15
  • By default a class subclasses `object`. – trincot Jul 26 '22 at 10:15
  • "Is it other classes that have been defined within the same file but further up to this class?" no definitely not. `super` will only ever refer to a parent class. As trincot noted, if no parent is given the parent will be `object`. – Anentropic Jul 26 '22 at 10:17
  • Thank Anentropic, all of it makes much more sense now! re super() – Patrick_Chong Jul 26 '22 at 10:23

2 Answers2

1

The *args, **kwargs here are used just to "redirect" the parameters from the child's constructor to the parent's construtor. It pretty much means take any arguments and call the parent's constructor with these arguments.

As an example we could define

def my_print(*args, **kwargs):
    print(*args, **kwargs)

By using *args and **kwargs we ensure that print receives exactly the same arguments as our function, so it is a perfect copy of the print function. We can for example call

my_print("foo", "bar", sep="+")

And we would receive the same exact output as if we called print. ( "foo+bar")

As per your other questions

For you first question, yes, super().__init__() ensures that the parent's constructor gets called.

And for the third question, you can read more about it in this question. In short super() doesn't do exactly what one might think at first and, there is always a base class, even if not explicitly mentioned (object).

Filip Müller
  • 1,096
  • 5
  • 18
  • Fllip, thanks so much for the above- it makes a lot of sense now. One other thing I noticed is that at times the class has a few arguments passed explicitly followed by the final argument of **kwargs? – Patrick_Chong Jul 26 '22 at 10:28
  • That is used to pass any keyword arguments to the base class to ensure that they still work in the child class. For example Tkinter components can take a lot of keyword arguments like `height`, `width`, `bg`, etc. If you want to inherit from one of those components and don't want to lose the ability to set these options in the child class, you need to pass these arguments to the parent's constructor. `**kwargs` is just a way to accept and pass further any non-specified keyword arguments. – Filip Müller Jul 26 '22 at 10:42
  • Maybe [this question](https://stackoverflow.com/questions/14626279/inheritance-best-practice-args-kwargs-or-explicitly-specifying-parameters) helps you understand better. Especially [this answer](https://stackoverflow.com/a/14626574/19589534). – Filip Müller Jul 26 '22 at 10:48
  • Thanks Fllip, the links are very helpful and the expalnation – Patrick_Chong Jul 26 '22 at 12:36
0

For single inheritance, your understanding of the behavior of super is essentially correct - you don't really need super when all you're dealing with is single inheritance.

For multiple inheritance it is not correct. There super will call the next class in self's MRO chain - which could be the parent, and could be a sibling class in case of diamond pattern inheritance.

Diamond inheritance pattern:

class A:
    def say(self):
        print('a')

class B(A):
    def say(self):
        super().say()
        print('b')

class C(A):
    def say(self):
        super().say()
        print('c')

class D(B, C):
    def say(self):
        super().say()
        print('d')


d = D()
d.say()

the inhertiance graph looks like a diamond:

enter image description here

which prints

a
c
b
d

because the super in D calls B.say, which uses super to call C.say, which uses super to call A.say. I.e. the super call in B calls a class that B doesn't know about.

thebjorn
  • 26,297
  • 11
  • 96
  • 138
  • Thank thebjorn, I am dealing with MULTIPLE inheritance! - this is where my understanding lacks a bit. Would I just follow the same logic as single inheritance up the chain? (I assume there is only one way/one syntax to achieve inheritance?) – Patrick_Chong Jul 26 '22 at 10:25
  • I can recommend you this video that helped me a lot in understanding how super works [super, Python's most misunderstood feature.](https://www.youtube.com/watch?v=X1PQ7zzltz4) – MangoNrFive Jul 26 '22 at 10:52
  • @Patrick_Chong I've updated my answer with an example that might help. – thebjorn Jul 26 '22 at 11:06
  • Hi thebjorn, I was tracing through the above, and can I confirm that class B DOES NOT inherit from class C? Or does it? – Patrick_Chong Jul 26 '22 at 14:05
  • It does not. The previous spelling of B's super, `super(B, self).say()`, makes it perhaps a little clearer what is going on: super looks in `self.__class__.__mro__` for the class that is after `B`. `self` is here an instance of `D` and `D.__mro__` is `[D, B, C, A]`, so the class after `B` in the super-chain is `C` (when viewed from a `D` instance). More details on mro (method-resolution order) and how it is constructed: https://en.wikipedia.org/wiki/C3_linearization (look through the references for much more detail). – thebjorn Jul 27 '22 at 09:00