Python3's super and comprehensions -> TypeError?

Python3's super and comprehensions -> TypeError?

在理解中使用 python3 的 super 似乎总是导致 TypeError: super(type, obj): obj must be an instance or subtype of type(但使用 python 2 的 super 确实按预期工作)

class A(object):
    def __repr__(self):
         return "hi!"  

class B(A):
    def __repr__(self):
         return "".join(super().__repr__() for i in range(2))  

repr(B())
#output: <repr(<__main__.B at 0x7f70cf36fcc0>) failed: TypeError: super(type, obj): obj must be an instance or subtype of type>

class C(A):
    def __repr__(self):
        s = ''
        for i in range(4):
            s += super().__repr__()
        return s     

repr(C())
#output: hi!hi!hi!hi!

class D(A):
    def __repr__(self):
        return "".join(super(D,self).__repr__() for i in range(4))

repr(D())
#output: hi!hi!hi!hi!

那么,为什么 new super() 在生成器推导中失败了?

附录:

In [28]: class E(A):
   ....:     def __repr__(self):
   ....:         def inner():
   ....:             print(repr(__class__))
   ....:         inner()
   ....:         return ''
In [29]: repr(E())
<class '__main__.E'>
Out[29]: ''

In [30]: class F(A):
   ....:     def __repr__(self):
   ....:         return "".join([super().__repr__() for i in range(4)])
   ....:     

In [31]: repr(F())

TypeError: super(type, obj): obj must be an instance or subtype of type

简单说明

查看 super() 的文档:

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.

在 class 定义中 他们的意思是 在 class 方法范围内 。在 class 方法范围内 解释器能够使用与您在 Python 中明确提供的参数相同的参数来完成零形式 2. 然而,列表理解创建了它自己的范围。这就是它失败的原因:您不是从 class 方法范围调用 super(),解释器无法使用所有参数完成它。

进阶说明

根据Python data model:

__class__ is an implicit closure reference created by the compiler if any methods in a class body refer to either __class__ or super. This allows the zero argument form of super() to correctly identify the class being defined based on lexical scoping, while the class or instance that was used to make the current call is identified based on the first argument passed to the method.

Python 能够从 __class__ 变量中收集 super() 的第一个参数,即使在列表理解中也是如此(因为它在所有子作用域中都可以作为任何通常的闭包使用)。您可以使用以下方法对其进行测试:

class T:
    def test(self):
        print(__class__)
        print([__class__ for _ in range(1)][0])


T().test()

将输出:

<class '__main__.T'>
<class '__main__.T'>

但是解释器错误地收集了 super() 的第二个参数:self。它假定对 super() 的调用发生在方法范围内,并尝试使用以下 C code 获取方法的第一个参数(为清楚起见,省略了许多行):

PyFrameObject *f = PyThreadState_GET()->frame;
obj = f->f_localsplus[0];
if (obj != NULL) {
    obj_type = supercheck(type, obj);
    if (obj_type == NULL)
        return -1;
    Py_INCREF(obj);
}

无法从 Python 访问 f->f_localsplus[0],但根据代码中的注释可以 contains "locals+stack"。所以我们可以利用 locals() 进行测试(不幸的是订单丢失了)。让我们测试一下,class 方法和列表理解中的局部变量可用的内容:

class T:
    def test(self):
        print(locals())
        print([locals() for _ in range(1)])


T().test()

将打印:

{'self': <__main__.T object at 0x100f1f8d0>}
{'_': 0, '.0': <range_iterator object at 0x100fbb2a0>}

在第一种情况下有一个对我们对象的引用,它会被解释器正确找到。在列表推导式中,字典中没有对象,所以它会得到 0range_iterator(记住,缺少顺序?)。这些都不是我们对象的实例。它会失败 supercheck() 并给你一个错误 obj must be an instance or subtype of type (例如 T)。


查看 here for more information on how super() is implemented and here 了解更多详细信息,了解为什么这样做。