一种将 NamedTuple 子类化以进行类型检查的方法
Posted
技术标签:
【中文标题】一种将 NamedTuple 子类化以进行类型检查的方法【英文标题】:A way to subclass NamedTuple for purposes of typechecking 【发布时间】:2017-11-01 10:14:45 【问题描述】:我有几个namedtuple
s 共享一些字段。我有一个接受这些元组的函数,并保证只与共享字段交互。我想在 mypy 中对此类代码进行类型检查。
代码示例如下:
from typing import NamedTuple
class Base(NamedTuple):
x: int
y: int
class BaseExtended(NamedTuple):
x: int
y: int
z: str
def DoSomething(tuple: Base):
return tuple.x + tuple.y
base = Base(3, 4)
base_extended = BaseExtended(5, 6, 'foo')
DoSomething(base)
DoSomething(base_extended)
当我在这段代码上运行 mypy 时,我得到一个可预测的错误:
mypy_example.py:20:错误:“DoSomething”的参数 1 具有不兼容的类型“BaseExtended”;预期的“基地”
有没有办法构造我的代码并保持 mypy 类型检查?我不能从Base
继承BaseExtended
,因为there's a bug 在NamedTuple
继承实现中。
我也不想使用丑陋的Union[Base, BaseExtended]
,因为当我尝试对List
进行类型检查时,这会中断,因为List[Union[Base, BaseExtended]]
不等于List[BaseExtended]
,因为some mypy magic about variant/covariant types。
我应该放弃这个想法吗?
【问题讨论】:
【参考方案1】:命名元组的构造方式使得从typing.NamedTuple
类继承是不可能的。您必须编写自己的元类来扩展 typing.NamedTupleMeta
类以使子类化工作,即使这样 the class generated by collections.namedtuple()
is just not built to extend。
相反,您想使用新的dataclasses
module 来定义您的类并实现继承:
from dataclasses import dataclass
@dataclass(frozen=True)
class Base:
x: int
y: int
@dataclass(frozen=True)
class BaseExtended(Base):
z: str
该模块是 Python 3.7 中的新模块,但您可以在 Python 3.6 上pip install dataclasses
the backport。
上面定义了两个具有x
和y
属性的不可变类,BaseExtended
类又增加了一个属性。 BaseExtended
是 Base
的完整子类,因此对于打字目的而言,它符合 DoSomething()
函数的要求。
这些类不是完整命名的元组,因为它们没有长度或不支持索引,但通过创建一个继承自collections.abc.Sequence
的基类,添加两个按索引访问字段的方法,可以轻松添加。如果您将 order=True
添加到 @dataclass()
装饰器,那么您的实例将变得完全可排序,就像(命名)元组一样:
from collections.abc import Sequence
from dataclasses import dataclass, fields
class DataclassSequence(Sequence):
# make a dataclass tuple-like by accessing fields by index
def __getitem__(self, i):
return getattr(self, fields(self)[i].name)
def __len__(self):
return len(fields(self))
@dataclass(frozen=True, order=True)
class Base(DataclassSequence):
x: int
y: int
MyPy will soon support dataclasses
explicitly;在 0.600 版中,您仍然会收到错误,因为它无法识别 dataclasses
模块导入或生成 __new__
方法。
在 Python 3.6 及更早的版本中,也可以安装attrs
project 来达到同样的效果;上面的序列基类使用attrs
:
from collections.abc import Sequence
import attr
class AttrsSequence(Sequence):
# make a dataclass tuple-like by accessing fields by index
def __getitem__(self, i):
return getattr(self, attr.fields(type(self))[i].name)
def __len__(self):
return len(attr.fields(type(self)))
@attr.s(frozen=True, auto_attribs=True)
class Base(AttrsSequence):
x: int
y: int
dataclasses
直接基于attrs
,attrs
提供更多功能; mypy 完全支持使用attrs
生成的类。
【讨论】:
我喜欢 NamedTuple 都有一个 _asdict(),所有数据类都有父类型吗?【参考方案2】:PEP 544 提议对类型系统进行扩展,以允许结构子类型(静态鸭子类型)。 typing.NamedTuple
的运行时实现也将很快得到改进,可能在 6 月底的 Python 3.6.2 中(这也将通过 PyPI 上的 typing
向后移植)。
【讨论】:
谢谢。我猜你不知道在 3.6.2 之前我可以使用任何优雅的解决方法吗?如果是这样,我会接受答案并继续前进。 @wuzwm 不,很遗憾我不知道。 改进尚未实现,同样的问题仍然适用于 Python 3.6.5 和 3.7.0b4。 实际上是故意放弃运行时更改。已决定命名元组应保持简约,并且对于所有复杂的用例,用户都应使用数据类。以上是关于一种将 NamedTuple 子类化以进行类型检查的方法的主要内容,如果未能解决你的问题,请参考以下文章