Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does any magic happen when I call `super(some_cls)`?

While investigating this question, I came across this strange behavior of single-argument super:

Calling super(some_class).__init__() works inside of a method of some_class (or a subclass thereof), but throws an exception when called anywhere else.

Code sample:

class A():                                                                                         
    def __init__(self):                                                         
        super(A).__init__()  # doesn't throw exception

a = A()
super(A).__init__()  # throws exception

The exception being thrown is

Traceback (most recent call last):
  File "untitled.py", line 8, in <module>
    super(A).__init__() # throws exception
RuntimeError: super(): no arguments

I don't understand why the location of the call makes a difference.

It's well-known that the zero-argument form of super performs magic:

The zero argument form only works inside a class definition, as the compiler fills in the necessary details to correctly retrieve the class being defined, as well as accessing the current instance for ordinary methods.

However, no such statement exists for the one-argument form of super. On the contrary:

Also note that, aside from the zero argument form, super() is not limited to use inside methods.


So, my question is, what exactly is happening under the hood? Is this the expected behavior?

like image 717
Aran-Fey Avatar asked Jan 17 '18 14:01

Aran-Fey


People also ask

Why do I have to call Super ()?

So, the short answer to "why do I have to call super ()" is... So that you can control when it's called and do things before, or after super () gets ran. :-) Show activity on this post. 1. Calls the superclass’ constructor. 2. Access a member of the superclass that has been hidden by a member of a subclass.

What happens if we call'Super ()'without a superclass in Java?

Since the class named Object is the superclass of all classes in Java. If you call "super ()" without any superclass, Internally, the default constructor of the Object class will be invoked (which displays nothing). //Nothing will be displayed……

What does it mean when a class calls the Super method?

When a class calls the super method, it means it is a subclass of a superclass. You can also see this from the extends keyword after Car. Languages vary in how they handle inheritance so I won't go into that any further. From the example I provided above, the Car class is a subclass of the React.Component superclass.

How does the magic super() __class__ cell work?

The magic super () __class__ cell sidesteps these issues nicely by giving you access to the original class object. The PEP was kicked off by Guido, who initially envisioned super becoming a keyword, and the idea of using a cell to look up the current class was also his.


1 Answers

In both cases, super(A) gives an unbound super object. When you call __init__() on that, it's being called with no arguments. When super.__init__ is called with no arguments, the compiler tries to infer the arguments: (from typeobject.c line 7434, latest source)

static int
super_init(PyObject *self, PyObject *args, PyObject *kwds)
{
    superobject *su = (superobject *)self;
    PyTypeObject *type = NULL;
    PyObject *obj = NULL;
    PyTypeObject *obj_type = NULL;

    if (!_PyArg_NoKeywords("super", kwds))
        return -1;
    if (!PyArg_ParseTuple(args, "|O!O:super", &PyType_Type, &type, &obj))
        return -1;

    if (type == NULL) {
        /* Call super(), without args -- fill in from __class__
           and first local variable on the stack. */

A few lines later: (ibid, line 7465)

    f = PyThreadState_GET()->frame;
...
    co = f->f_code;
...
    if (co->co_argcount == 0) {
        PyErr_SetString(PyExc_RuntimeError,
                        "super(): no arguments");
        return -1;
    }

When you call super(A), this inferring behavior is bypassed because type is not None. When you then call __init__() on the unbound super - because it isn't bound, this __init__ call isn't proxied - the type argument is None and the compiler attempts to infer. Inside the class definition, the self argument is present and is used for this purpose. Outside, no arguments are available, so the exception is raised.

In other words, super(A) is not behaving differently depending on where it is called - it's super.__init__() that's behaving differently, and that's exactly what the documentation suggests.

like image 80
Nathan Vērzemnieks Avatar answered Sep 27 '22 17:09

Nathan Vērzemnieks