出于类型检查的目的将 NamedTuple 子类化的方法 [英] A way to subclass NamedTuple for purposes of typechecking

查看:61
本文介绍了出于类型检查的目的将 NamedTuple 子类化的方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有几个共享一些字段的命名元组.我有一个接受这些元组并保证只与共享字段交互的函数.我想在 mypy 中对此类代码进行类型检查.

I have several namedtuples that share some fields. I have a function that accepts these tuples and is guaranteed to only interact with the shared fields. I want to typecheck such code in 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 时,我得到一个可预测的错误:

When I run mypy on this code I get a predictable error:

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

mypy_example.py:20: error: Argument 1 to "DoSomething" has incompatible type "BaseExtended"; expected "Base"

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

Is there no way to structure my code and keep mypy typechecking? I cannot inherit BaseExtended from Base, since there's a bug in the NamedTuple inheritance implementation:

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

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

I don't want to use an ugly "Union[Base, BaseExtended]" either, since this breaks when I try to typecheck a List, since "List[Union[Base, BaseExtended]]" is not equal to "List[BaseExtended]" due to some mypy magic about variant/covariant types:

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

我应该放弃这个想法吗?

Should I just abandon the idea?

推荐答案

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

The way named tuples are constructed make inheritance from typing.NamedTuple classes as yet not possible. You'd have to write your own metaclass to extend the typing.NamedTupleMeta class to make subclassing work, and even then the class generated by collections.namedtuple() is just not built to extend.

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

Instead, you want to use the new dataclasses module to define your classes and achieve inheritance:

from dataclasses import dataclass

@dataclass(frozen=True)
class Base:
    x: int
    y: int

@dataclass(frozen=True)
class BaseExtended(Base):
    z: str

该模块是 Python 3.7 中的新模块,但您可以pip install dataclasses在 Python 3.6 上向后移植.

The module is new in Python 3.7 but you can pip install dataclasses the backport on Python 3.6.

上面定义了两个具有 xy 属性的不可变类,其中 BaseExtended 类增加了一个属性.BaseExtendedBase 的完整子类,因此对于输入而言,符合 DoSomething() 函数的要求.

The above defines two immutable classes with x and y attributes, with the BaseExtended class adding one more attribute. BaseExtended is a full subclass of Base, so for typing purposes fits the requirements for the DoSomething() function.

这些类不是全命名元组,因为它们没有长度或支持索引,但是通过创建一个继承自 collections.abc.Sequence 的基类,添加两个方法,可以轻松添加按索引访问字段.如果您将 order=True 添加到 @dataclass() 装饰器,那么您的实例将变得完全可排序,就像(命名的)元组一样:

The classes are not full named tuples, as they don't have a length or support indexing, but that's trivially added by creating a baseclass that inherits from collections.abc.Sequence, adding two methods to access fields by index. If you add order=True to the @dataclass() decorator then your instances become fully orderable the same way (named) tuples are:

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 将很快明确支持数据类;在 0.600 版本中,您仍然会遇到错误,因为它无法识别 dataclasses 模块导入或生成了 __new__ 方法.

MyPy will soon support dataclasses explicitly; in version 0.600 you'll get errors still as it doesn't recognise the dataclasses module import or that a __new__ method is generated.

在 Python 3.6 及更早版本中,您还可以安装 attrs 项目 达到同样的效果;上面的序列基类使用 attrs 如下所示:

In Python 3.6 and earlier, you can also install the attrs project to achieve the same effects; the above sequence base class looks like this using 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 生成的类.

dataclasses is directly based on attrs, with attrs providing more functionality; mypy fully supports classes generated with attrs.

这篇关于出于类型检查的目的将 NamedTuple 子类化的方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆