Python3的超级和理解 - >类型错误?

时间:2015-08-08 15:01:17

标签: python python-3.x list-comprehension super

在理解中使用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!

那么,为什么新的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

1 个答案:

答案 0 :(得分:11)

简单说明

查看super()的文档:

  

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

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

高级说明

根据Python data model

  

__class__是编译器创建的隐式闭包引用,如果类主体中的任何方法引用__class__super。这允许super()的零参数形式正确地识别基于词法作用域定义的类,而用于进行当前调用的类或实例是基于传递给方法的第一个参数来识别的。 / p>

Python能够从super()变量收集__class__的第一个参数,甚至在列表理解中(因为它在所有子范围中都可用,就像任何常见的闭包一样)。您可以使用以下方法对其进行测试:

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&#34;本地人+堆栈&#34;根据代码中的评论。因此我们可以利用locals()进行测试(遗憾的是订单缺失)。让我们测试一下,类方法和列表理解中的本地人可用的是什么:

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以获取有关super()的实施方式的详细信息,并here详细了解为何会这样做。