Python3的超级和理解 - > TypeError?

Nig*_*een 10 python list-comprehension super python-3.x

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

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!
Run Code Online (Sandbox Code Playgroud)

那么,为什么新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
Run Code Online (Sandbox Code Playgroud)

Yar*_*min 14

简单的解释

查看以下文档super():

零参数形式仅适用于类定义,因为编译器填写必要的细节以正确检索正在定义的类,以及访问普通方法的当前实例.

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

高级解释

根据Python 数据模型:

__class__是由编译器创建如果在类身体的任何方法指任一个隐式的闭合参考__class__super.这允许零参数形式super()正确地识别基于词法作用域定义的类,而用于进行当前调用的类或实例基于传递给该方法的第一个参数来识别.

Python能够super()__class__变量中收集第一个参数甚至是列表推导内部(因为它在所有子范围中都可用,就像任何常见的闭包一样).你可以用以下方法测试它:

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


T().test()
Run Code Online (Sandbox Code Playgroud)

将输出:

<class '__main__.T'>
<class '__main__.T'>
Run Code Online (Sandbox Code Playgroud)

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

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);
}
Run Code Online (Sandbox Code Playgroud)

它不可能f->f_localsplus[0]从Python 访问,但它根据代码中的注释包含 "locals + stack".所以我们可以利用locals()测试(不幸的是订单缺失).让我们测试一下,类方法和列表理解中的本地可用内容是什么:

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


T().test()
Run Code Online (Sandbox Code Playgroud)

将打印:

{'self': <__main__.T object at 0x100f1f8d0>}
{'_': 0, '.0': <range_iterator object at 0x100fbb2a0>}
Run Code Online (Sandbox Code Playgroud)

在第一种情况下,我们的对象有一个引用,并且它将由解释器正确找到.在列表内容理解中,字典中没有对象,所以它会得到0或者range_iterator(记住,订单丢失了吗?).这些都不是我们对象的实例.它会失败supercheck()并给你一个错误obj must be an instance or subtype of type(例如T).


看看这里有关如何super()实现的更多信息,这里有更多详细信息,为什么这样做.