24

我有几个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 类型检查?我不能BaseExtended从继承Base,因为继承实现中有一个错误。NamedTuple

我也不想使用丑陋Union[Base, BaseExtended]的,因为当我尝试对 a 进行类型检查时,这会中断,List因为List[Union[Base, BaseExtended]]不等于mypy 关于变体/协变类型的魔法List[BaseExtended]

我应该放弃这个想法吗?

4

2 回答 2

30

命名元组的构造方式使得从typing.NamedTuple类继承是不可能的。您必须编写自己的元类来扩展typing.NamedTupleMeta该类以使子类化工作,即使那样生成的类collections.namedtuple()也不是为扩展而构建的

相反,您想使用新dataclasses模块来定义您的类并实现继承:

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向后移植。

上面定义了两个具有xy属性的不可变类,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很快就会dataclasses明确支持;在 0.600 版本中,您仍然会收到错误,因为它无法识别dataclasses模块导入或__new__生成方法。

在 Python 3.6 及更早的版本中,也可以安装attrs项目来达到同样的效果;上面的序列基类看起来像这样使用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直接基于attrsattrs提供更多功能;mypy 完全支持使用attrs.

于 2018-05-16T11:36:05.153 回答
2

PEP 544提出了对类型系统的扩展,该扩展将允许结构子类型(静态鸭子类型)。运行时实现也typing.NamedTuple将很快得到改进,可能在 6 月底的 Python 3.6.2 中(这也将通过typingPyPI 进行反向移植)。

于 2017-06-04T09:16:28.113 回答