14

A colleague of mine wrote code analogous to the following today, asked me to have a look, and it took me a while to spot the mistake:

class A():                                                                                         
    def __init__(self):                                                         
        print('A')                                                              

class B(A):                                                                     
    def __init__(self):                                                         
        super(B).__init__()                                               

b = B()

The problem here is that there's no self parameter to super() in B's constructor. What surprised me is that absolutely nothing happens in this case, i.e. no error, nothing. What does the super object created by super(B) contain? As an object, it clearly has a constructor, so that's what gets called, but how is that object related to B? In particular, why is this valid code and doesn't throw an exception somewhere? Is super(B) an object with some actual use and what would that be?

Mazdak
  • 105,000
  • 18
  • 159
  • 188
Edvard Fagerholm
  • 862
  • 5
  • 15
  • 1
    Are you in Python 2 or 3? – James Jan 17 '18 at 13:31
  • The documentation on [`super`](https://docs.python.org/2/library/functions.html#super) states that omitting the second argument causes it to create an unbound object – UnholySheep Jan 17 '18 at 13:39
  • What's even more odd is that calling `super(B).__init__()` only works inside of a method of `B` (or a subclass of `B`). Calling `super(B).__init__()` outside of a class throws `RuntimeError: super(): no arguments` – Aran-Fey Jan 17 '18 at 13:59
  • [Here's](http://www.artima.com/weblogs/viewpost.jsp?thread=236278) a very nice article about unbound super objects. – Aran-Fey Jan 17 '18 at 14:02

2 Answers2

6

The only thing that causes all these ambiguities is that "why obj = super(B).__init__() works?". That's because super(B).__self_class__ returns None and in that case you're calling the None objects' __init__ like following which returns None:

In [40]: None.__init__()

Regarding the rest of the cases, you can simply check the difference by calling the super's essential attributes in both cases:

In [36]: class B(A):                                                                     
        def __init__(self):                                                         
                obj = super(B, self)
                print(obj.__class__)
                print(obj.__thisclass__)
                print(obj.__self_class__)
                print(obj.__self__)
   ....:         

In [37]: b = B()
<class 'super'>
<class '__main__.B'>
<class '__main__.B'>
<__main__.B object at 0x7f15a813f940>

In [38]: 

In [38]: class B(A):                                                                     
        def __init__(self):                                                         
                obj = super(B)
                print(obj.__class__)
                print(obj.__thisclass__)
                print(obj.__self_class__)
                print(obj.__self__)
   ....:         

In [39]: b = B()
<class 'super'>
<class '__main__.B'>
None
None

For the rest of the things I recommend you to read the documentation thoroughly. https://docs.python.org/3/library/functions.html#super and this article by Raymond Hettinger https://rhettinger.wordpress.com/2011/05/26/super-considered-super/.

Moreover, If you want to know why super(B) doesn't work outside of the class and generally why calling the super() without any argument works inside a class you can read This comprehensive answer by Martijn https://stackoverflow.com/a/19609168/2867928.

A short description of the solution:

As mentioned in the comments by @Nathan Vērzemnieks you need to call the initializer once to get the super() object work. The reason is laid behind the magic of new super object that is explained in aforementioned links.

In [1]: class A:
   ...:     def __init__(self):
   ...:         print("finally!")
   ...:

In [2]: class B(A):
   ...:     def __init__(self):
   ...:         sup = super(B)
   ...:         print("Before: {}".format(sup))
   ...:         sup.__init__()
   ...:         print("After: {}".format(sup))
   ...:         sup.__init__()
   ...:

In [3]: B()
Before: <super: <class 'B'>, NULL>
After: <super: <class 'B'>, <B object>>
finally!
Mazdak
  • 105,000
  • 18
  • 159
  • 188
  • @Rawing Isn't that explained in https://rhettinger.wordpress.com/2011/05/26/super-considered-super/? – Mazdak Jan 17 '18 at 14:12
  • @Rawing That's strongly related to the magic of `super()` in python 3.X which can be called without arguments inside a class. Here is a question regard this very topic https://stackoverflow.com/questions/19608134/why-is-python-3-xs-super-magic that is answered nicely by Martijn. – Mazdak Jan 17 '18 at 14:20
  • As far as I can tell, that only applies to the 0-argument form of `super`. Anyway, I'll stop pestering you (sorry about that!) and post a new question about it. Let's clean up this mess, shall we? – Aran-Fey Jan 17 '18 at 14:26
  • I don't think it's true that `super(B).__init__()` is calling `None.__init__()`. See this gist for evidence to the contrary: https://gist.github.com/njvrzm/358f96c971a2adc329bcc1d860bede2a – Nathan Vērzemnieks Jan 19 '18 at 01:17
  • Actually I've copied the code from that gist to [my answer to this related question](https://stackoverflow.com/a/48332210/9200529). – Nathan Vērzemnieks Jan 19 '18 at 01:44
  • @NathanVērzemnieks Well that's after calling the initializer once! All I was explained was that when you don't pass the `self` to the `super()` the `__self_class__` is None and therefore, there's nothing to be called. But as it's explained in aforementioned links the solution is what you did. You have to call the initializer manually once! (the magic of super) – Mazdak Jan 19 '18 at 08:59
  • My point is, you say `super(B).__init__()` calls `None.__init__()` but it doesn't - it calls super's initializer twice! Once as part of `super(B)` and once explicitly. `None.__init__` has no part in it. – Nathan Vērzemnieks Jan 19 '18 at 14:44
  • @NathanVērzemnieks I know, but [`__self_class__` is the type of the instance invoking `super()`](https://github.com/python/cpython/blob/ce5b0e9db1b9698e6ffc43ae41cf3a22ca5a6ba6/Objects/typeobject.c#L7454) which before calling the `__init__` is None, as mentioned in documentation as well. – Mazdak Jan 19 '18 at 15:02
  • Ok, I'm not sure who's misunderstanding what here, but I'll try one more approach: when you call `__init__` on an unbound super like you get from `super(B)`, no delegation happens: you're calling `super.__init__`. If it were otherwise, if it was actually calling `None.__init__`, the "before" and "after" outputs from my code would be the same. – Nathan Vērzemnieks Jan 19 '18 at 16:01
2

The confusion here comes from the fact that (in a class definition context) super() gives a bound super object which then delegates __init__ to its __self_class__, while super(B) creates an unbound super object which, because its __self_class__ is None, does not delegate.

In [41]: class Test(int):
    ...:     def __init__(self):
    ...:         print(super().__self_class__)
    ...:         print(super().__init__)
    ...:         print(super(Test).__self_class__)
    ...:         print(super(Test).__init__)
    ...:

In [42]: Test()
<class '__main__.Test'>
<method-wrapper '__init__' of Test object at 0x10835c9c8>
None
<method-wrapper '__init__' of super object at 0x10835c3c8>

So when you call super(B).__init__(), it creates an unbound super but then immediately calls __init__ on it; that, because of the magic described in the various links in this other answer, binds that unbound super. There are no references to it, so it disappears, but that's what's happening under the hood.

Nathan Vērzemnieks
  • 5,495
  • 1
  • 11
  • 23