我有几个共享某些字段的命名元组。我有一个接受这些元组的函数,并保证只与共享字段交互。我想在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 typechecking?我不能从Base继承BaseExtended,因为NamedTuple继承实现中存在一个错误:
https://github.com/python/typing/issues/427
我也不想使用丑陋的“Union [Base,BaseExtended]”,因为当我尝试对类型列表进行类型检查时会出现这种情况,因为“List [Union [Base,BaseExtended]]”不等于“列出[BaseExtended]“由于关于变体/协变类型的一些mypy魔法:
https://github.com/python/mypy/issues/3351
我应该放弃这个想法吗?
答案 0 :(得分:9)
构造名为元组的方法使得从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
生成的类。
答案 1 :(得分:1)
有PEP 544提出了一个扩展类型系统,它将允许结构子类型(静态鸭子类型)。此外,typing.NamedTuple
的运行时实现很快就会得到改进,可能是在6月底的Python 3.6.2中(这也将在PyPI上通过typing
向后移植)。