Search code examples
pythonpython-3.xstatic-methodssuper

Why doesn't super() work with static methods other than __new__?


I understand that __new__ is a static method and super() can be called from it create a new object, like so:

>>> class A:
...     def __new__(cls):
...         print('__new__ called')
...         return super().__new__(cls)
...
>>> a = A()
__new__ called

Why doesn't the super call work with other static methods? Why does the following fail?

>>> class B:
...     @staticmethod
...     def funcB():
...         print('funcB called')
...
>>> class C(B):
...     @staticmethod
...     def funcC():
...         print('funcC called')
...         super().funcB()
...
>>> c = C()
>>> c.funcC()
funcC called
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in funcC
RuntimeError: super(): no arguments

Solution

  • super() with no arguments in Python 3 is basically a hack over its argument based version.

    When super() gets no arguments it fetches the first argument i.e the class using a special cell variable named __class__ and for second argument it will get the first local variable from the stack(which is going to be function's first argument).

    In case of __new__ it can get both(__class__ and cls) and works fine.

    But in this case for example there's no second variable available apart from __class__ hence it fails.

    class A:
      @staticmethod
      def func():
        super().func()  # super(__class__, <missing>).func()
    
    
    A().func()  # RuntimeError: super(): no arguments
    

    Now if we change it to accept an argument then things change:

    class A:
      @staticmethod
      def func(foo):
        super().func()
    
    
    # This fails because super(B, 1).func() doesn't make sense.
    A().func(1)  # TypeError: super(type, obj): obj must be an instance or subtype of type
    # Works! But as there's no parent to this class with func() it fails as expected.
    A().func(A())  # AttributeError: 'super' object has no attribute 'func'
    

    Hence the only solution is to make the things explicit with super() in your case:

    super(C, C).funcC()
    

    In general I am not sure why the implementation in case of staticmethod can't make an exception and use __class__ for both the arguments to make it work.


    Related CPython code:

    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. */
            PyFrameObject *f;
            PyCodeObject *co;
            Py_ssize_t i, n;
            f = PyThreadState_GET()->frame;
            if (f == NULL) {
                PyErr_SetString(PyExc_RuntimeError,
                                "super(): no current frame");
                return -1;
            }
            co = f->f_code;
            if (co == NULL) {
                PyErr_SetString(PyExc_RuntimeError,
                                "super(): no code object");
                return -1;
            }
            if (co->co_argcount == 0) {
                PyErr_SetString(PyExc_RuntimeError,
                                "super(): no arguments");
                return -1;
            }
            ...