`typing.NamedTuple` 子类中的 `super` 在 python 3.8 中失败
`super` in a `typing.NamedTuple` subclass fails in python 3.8
我的代码在 Python 3.6 中有效,但在 Python 3.8 中失败。它似乎归结为在 typing.NamedTuple
的子类中调用 super
,如下所示:
<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__?
In [3]: class Test(typing.NamedTuple):
...: a: int
...: b: float
...: #def __repr__(self):
...: # return super(object, self).__repr__()
...:
>>> # works
此 super(object, self).__repr__
调用的目的是使用标准 '<__main__.Test object at 0x7fa109953cf8>'
__repr__
而不是打印出元组元素的所有内容(默认情况下会发生)。 super
上有 some 导致类似的错误,但它们:
- 参考无参数版本
super()
- 在 Python 3.6 中已经失败(在 3.6 -> 3.8 升级之前对我有用)
- 我还是不明白如何解决这个问题,因为它不是我可以控制的自定义元类,而是 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
有效(直到我们实际调用 __repr__
),即使 foo
是一个未定义的引用。 super
在这方面很神奇吗?
不幸的是,我对 CPython 的内部结构和 类 代不太熟悉,无法解释它失败的原因,但是有 this CPython bug tracker issue which seems to be related and some words in Python docs
CPython implementation detail: In CPython 3.6 and later, the __class__
cell is passed to the metaclass as a __classcell__
entry in the class namespace. If present, this must be propagated up to the type.__new__
call in order for the class to be initialised correctly. Failing to do so will result in a RuntimeError
in Python 3.8.
所以可能在实际 namedtuple
创建过程中的某个地方,我们调用了 type.__new__
而没有 __classcell__
传播,但我不知道是不是这样。
但这种特殊情况似乎可以通过不使用 super()
调用来解决,并明确说明 "we need to have __repr__
method of the object
class" 喜欢
class Test(typing.NamedTuple):
a: int
b: float
__repr__ = object.__repr__
我在 (我刚刚更新)中有点错误。
显然,这种行为在 super
的 两个 案例中都有体现。事后看来,我应该测试一下。
这里发生的是 metaclass NamedTupleMeta
确实没有将 __classcell__
传递给 type.__new__
因为它动态创建了一个命名元组 returns那个。实际上,在 Python 的 3.6 和 3.7 中(这仍然是 DeprecationWarning
),__classcell__
泄漏到 class 字典中,因为它没有被 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>
按照 Azat 的建议直接使用 object.__repr__
即可。
how can this fail at definition time
以下同样失败:
class Foo(metaclass=1): pass
在构建 class 时会执行许多 检查。其中,正在检查 metaclass 是否已将 __classcell__
传递给 type_new
。
以下:https://bugs.python.org/issue42765?
这个技巧(不使用 super())救了我:
return 元组.getitem(self, index_or_key)
另一种似乎至少对我有用的解决方法如下:
class BaseTest(typing.NamedTuple):
a: int
b: float
class Test(BaseTest):
def __repr__(self):
return super().__repr__()
通过子class基于 NameTuple 的 class,似乎 class 单元格被删除了。
我的代码在 Python 3.6 中有效,但在 Python 3.8 中失败。它似乎归结为在 typing.NamedTuple
的子类中调用 super
,如下所示:
<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__?
In [3]: class Test(typing.NamedTuple):
...: a: int
...: b: float
...: #def __repr__(self):
...: # return super(object, self).__repr__()
...:
>>> # works
此 super(object, self).__repr__
调用的目的是使用标准 '<__main__.Test object at 0x7fa109953cf8>'
__repr__
而不是打印出元组元素的所有内容(默认情况下会发生)。 super
上有 some
- 参考无参数版本
super()
- 在 Python 3.6 中已经失败(在 3.6 -> 3.8 升级之前对我有用)
- 我还是不明白如何解决这个问题,因为它不是我可以控制的自定义元类,而是 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
有效(直到我们实际调用 __repr__
),即使 foo
是一个未定义的引用。 super
在这方面很神奇吗?
不幸的是,我对 CPython 的内部结构和 类 代不太熟悉,无法解释它失败的原因,但是有 this CPython bug tracker issue which seems to be related and some words in Python docs
CPython implementation detail: In CPython 3.6 and later, the
__class__
cell is passed to the metaclass as a__classcell__
entry in the class namespace. If present, this must be propagated up to thetype.__new__
call in order for the class to be initialised correctly. Failing to do so will result in aRuntimeError
in Python 3.8.
所以可能在实际 namedtuple
创建过程中的某个地方,我们调用了 type.__new__
而没有 __classcell__
传播,但我不知道是不是这样。
但这种特殊情况似乎可以通过不使用 super()
调用来解决,并明确说明 "we need to have __repr__
method of the object
class" 喜欢
class Test(typing.NamedTuple):
a: int
b: float
__repr__ = object.__repr__
我在 super
的 两个 案例中都有体现。事后看来,我应该测试一下。
这里发生的是 metaclass NamedTupleMeta
确实没有将 __classcell__
传递给 type.__new__
因为它动态创建了一个命名元组 returns那个。实际上,在 Python 的 3.6 和 3.7 中(这仍然是 DeprecationWarning
),__classcell__
泄漏到 class 字典中,因为它没有被 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>
按照 Azat 的建议直接使用 object.__repr__
即可。
how can this fail at definition time
以下同样失败:
class Foo(metaclass=1): pass
在构建 class 时会执行许多 检查。其中,正在检查 metaclass 是否已将 __classcell__
传递给 type_new
。
以下:https://bugs.python.org/issue42765?
这个技巧(不使用 super())救了我: return 元组.getitem(self, index_or_key)
另一种似乎至少对我有用的解决方法如下:
class BaseTest(typing.NamedTuple):
a: int
b: float
class Test(BaseTest):
def __repr__(self):
return super().__repr__()
通过子class基于 NameTuple 的 class,似乎 class 单元格被删除了。