`typing.NamedTuple` 子类中的 `super` 在 python 3.8 中失败

Jat*_*aki 11 python super python-3.6 python-3.8

我的代码在 Python 3.6 中运行,但在 Python 3.8 中失败。似乎归结为调用super的子类typing.NamedTuple,如下所示:

<ipython-input-2-fea20b0178f3> in <module>
----> 1 class Test(typing.NamedTuple):
      2     a: int
      3     b: float
      4     def __repr__(self):
      5         return super(object, self).__repr__()

RuntimeError: __class__ not set defining 'Test' as <class '__main__.Test'>. Was __classcell__ propagated to type.__new__?
Run Code Online (Sandbox Code Playgroud)
In [3]: class Test(typing.NamedTuple): 
   ...:     a: int 
   ...:     b: float 
   ...:     #def __repr__(self): 
   ...:     #    return super(object, self).__repr__() 
   ...:                                                                         

>>> # works
Run Code Online (Sandbox Code Playgroud)

super(object, self).__repr__调用的目的是使用标准'<__main__.Test object at 0x7fa109953cf8>' __repr__而不是打印出元组元素的所有内容(默认情况下会发生这种情况)。有一些 关于导致类似错误的问题super但它们:

  1. 参考无参数版本 super()
  2. 在 Python 3.6 中已经失败(它在 3.6 -> 3.8 升级之前对我有用)
  3. 无论如何,我无法理解如何解决此问题,因为它不是我可以控制的自定义元类,而是 stdlib 提供的typing.NamedTuple.

我的问题是如何解决这个问题,同时保持与 Python 3.6 的向后兼容性(否则我只是使用@dataclasses.dataclass而不是继承自typing.NamedTuple)?

一个附带问题是鉴于违规super调用位于尚未执行的方法中,这怎么会在定义时失败。例如:

In [3]: class Test(typing.NamedTuple): 
   ...:     a: int 
   ...:     b: float 
   ...:     def __repr__(self): 
   ...:         return foo 
Run Code Online (Sandbox Code Playgroud)

有效(直到我们实际调用__repr__),即使foo是未定义的引用。就是super在这方面的神奇吗?

Aza*_*kov 5

不幸的是,我不太熟悉 CPython 内部和类生成来说明它为什么失败,但是有这个 CPython 错误跟踪器问题似乎与Python 文档中的一些词有关

CPython 实现细节:在 CPython 3.6 及更高版本中,__class__单元格作为__classcell__类命名空间中的条目传递给元类。如果存在,则必须将其传播到type.__new__调用才能正确初始化该类。否则将导致RuntimeErrorPython 3.8中的 a 。

所以可能在实际namedtuple创建过程中的某个地方,我们有一个type.__new__没有__classcell__传播的调用,但我不知道是不是这种情况。

但是这种特殊情况似乎可以通过不使用super()call 并明确地说“我们需要拥有类的__repr__方法object”来解决,例如

class Test(typing.NamedTuple):
    a: int
    b: float
    __repr__ = object.__repr__
Run Code Online (Sandbox Code Playgroud)


Jim*_*ard 5

我在另一个问题(我刚刚更新)中略有错误。显然,这种行为在的两种情况下都有体现super。事后看来,我应该对此进行测试。

这里发生的情况是元类NamedTupleMeta确实没有传递__classcell__到,type.__new__因为它动态创建一个命名元组并返回它。实际上,在 Python 3.6 和 3.7 中(这仍然是DeprecationWarning),__classcell__泄漏到类字典中,因为它没有被 删除NamedTupleMeta.__new__

class Test(NamedTuple):
    a: int
    b: float
    def __repr__(self):
        return super().__repr__()

# isn't removed by NamedTupleMeta
Test.__classcell__
<cell at 0x7f956562f618: type object at 0x5629b8a2a708>
Run Code Online (Sandbox Code Playgroud)

按照 Azat 的建议直接使用object.__repr__就可以了。

这怎么会在定义时失败

同样的方式,以下也失败:

class Foo(metaclass=1): pass
Run Code Online (Sandbox Code Playgroud)

在构造类时会执行许多检查。其中,检查元类是否已传递__classcell__type_new.