为了类型检查而将NamedTuple子类化的方法

wuz*_*zwm 13 python typing namedtuple mypy

我有几个分享一些字段的命名元组.我有一个接受这些元组的函数,并保证只与共享字段交互.我想在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)
Run Code Online (Sandbox Code Playgroud)

当我在这段代码上运行mypy时,我得到一个可预测的错误:

mypy_example.py:20:错误:"DoSomething"的参数1具有不兼容的类型"BaseExtended"; 预期"基地"

有没有办法构建我的代码并保持mypy typechecking?我不能从Base继承BaseExtended,因为NamedTuple继承实现中存在一个错误:

https://github.com/python/typing/issues/427

我也不想使用一个丑陋的"Union [Base,BaseExtended]",因为当我尝试对一个List进行类型检查时会出现这种情况,因为"List [Union [Base,BaseExtended]]"不等于"List [BaseExtended] ]"由于关于变体/协变类型的一些mypy魔术:

https://github.com/python/mypy/issues/3351

我应该放弃这个想法吗?

Mar*_*ers 15

构造名为元组的方法使得从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
Run Code Online (Sandbox Code Playgroud)

该模块是Python 3.7中的新增功能,但您可以在Python 3.6上pip install dataclasses使用backport.

以上定义了两个一成不变的类与xy属性,与BaseExtended类中添加一个多个属性.BaseExtended是一个完整的子类Base,因此为了打字目的符合DoSomething()函数的要求.

这些类不是完整的命名元组,因为它们没有长度或支持索引,但通过创建继承的基类来collections.abc.Sequence添加,通过索引添加两个方法来访问字段:

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
Run Code Online (Sandbox Code Playgroud)

MyPy 很快会order=True明确支持 ; 在版本0.600中,您仍会收到错误,因为它无法识别@dataclass()模块导入或dataclasses生成方法.

在Python 3.6及更早版本中,您还可以安装dataclasses项目以实现相同的效果; 上面的序列基类看起来像这样使用__new__:

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
Run Code Online (Sandbox Code Playgroud)

attrs直接基于attrs,dataclasses提供更多功能; mypy完全支持使用生成的类attrs.

  • @Edmondo1984:数据类具有模块级“dataclasses.asdict()”函数](https://docs.python.org/3/library/dataclasses.html#dataclasses.asdict)。您始终可以混合定义“asdict()”或“_asdict()”的实用程序类或您希望在数据类上使用的其他方法。 (2认同)