当我调用 `super(some_cls)` 时会发生什么神奇的事情吗?

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

在调查 时,我遇到了单参数 super:

这种奇怪的行为

调用 super(some_class).__init__()some_class(或其子类)的方法内部工作,但在其他地方调用时会抛出异常。

代码示例:

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

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

抛出的异常是

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

我不明白为什么调用的位置会有所不同。

众所周知 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.

然而,super 的单参数形式不存在这样的语句。反之:

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


那么,我的问题是,幕后到底发生了什么?这是预期的行为吗?

(注意 - 我已经对这个答案进行了大量编辑,使其与实际问题更相关。)

如果调用 sup = super(A); sup.__init__(),结果与调用 sup = super() 完全相同:在 class 定义中,您得到一个绑定的超级;在外面,你会得到一个 RuntimeError(原因在我的另一个答案中)。

这里有一个片段可以证实:

In [1]: class A:
   ...:     def __init__(self):
   ...:         print("finally!")
   ...:
   ...:
   ...: class B(A):
   ...:     def __init__(self):
   ...:         noarg = super()
   ...:         print("No-arg super: {}".format(noarg))
   ...:         onearg = super(B) # creates unbound super
   ...:         print("One-arg before: {}".format(onearg))
   ...:         onearg.__init__() # initializes sup as if sup = super()
   ...:         print("One-arg after: {}".format(onearg))
   ...:         onearg.__init__() # calls B.__init__()
   ...:

In [2]: B()
No-arg super: <super: <class 'B'>, <B object>>
One-arg before: <super: <class 'B'>, NULL>
One-arg after: <super: <class 'B'>, <B object>>
finally!

在这两种情况下,super(A) 给出了一个未绑定的超级对象。当你在上面调用 __init__() 时,它是在没有参数的情况下调用的。当不带参数调用 super.__init__ 时,编译器会尝试推断参数:(来自 typeobject.c 行 7434,最新来源)

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. */

几行之后:(同上,第 7465 行)

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

当您调用 super(A) 时,会绕过此推断行为,因为类型不是 None。然后当您在未绑定的 super 上调用 __init__() - 因为它未绑定,所以此 __init__ 调用未被代理 - 类型参数 None 并且编译器尝试推断。在 class 定义中,存在 self 参数并用于此目的。在外面,没有可用的参数,所以抛出异常。

换句话说,super(A) 而不是 根据调用位置的不同表现不同 - super.__init__() 表现不同,而这正是文档建议。