web-dev-qa-db-ja.com

タイプチェックの目的でNamedTupleをサブクラス化する方法

いくつかのフィールドを共有する名前付きタプルがいくつかあります。これらのタプルを受け入れ、共有フィールドとのみ対話することが保証されている関数があります。 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のタイプチェックを続ける方法はありませんか? NamedTuple継承の実装にバグがあるため、BaseからBaseExtendedを継承できません。

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

醜い「Union [Base、BaseExtended]」も使いたくありません。「List [Union [Base、BaseExtended]]」は「List [BaseExtended]」と等しくないため、リストをタイプチェックしようとすると壊れてしまうからです。 ] "バリアント/共変タイプに関するいくつかのmypy魔法による:

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

私はその考えを捨てるべきですか?

15
wuzwm

名前付きタプルの構築方法により、_typing.NamedTuple_クラスからの継承はまだ不可能です。サブクラス化を機能させるには、_typing.NamedTupleMeta_クラスを拡張する独自のメタクラスを作成する必要がありますが、それでも collections.namedtuple()によって生成されたクラスは拡張用に構築されていません

代わりに、新しい 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で新しく追加されましたが、 _pip install dataclasses_ the backport on Python 3.6。

上記は、x属性とy属性を持つ2つの不変クラスを定義し、BaseExtendedクラスはもう1つの属性を追加します。 BaseExtendedBaseの完全なサブクラスであるため、入力の目的でDoSomething()関数の要件に適合します。

クラスは長さやインデックス作成をサポートしていないため、完全な名前のタプルではありませんが、_collections.abc.Sequence_から継承する基本クラスを作成し、インデックスでフィールドにアクセスする2つのメソッドを追加することで、簡単に追加できます。 _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 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
_

dataclassesattrsに直接基づいており、attrsはより多くの機能を提供します。 mypyは、attrsで生成されたクラスを完全にサポートします。

11
Martijn Pieters

PEP 544 は、構造的サブタイピング(静的ダックタイピング)を可能にする型システムの拡張を提案しています。また、typing.NamedTupleのランタイム実装は、おそらくPython 3.6.2 6月末に)すぐに改善されます(これもPyPIのtypingを介してバックポートされます)。

1
ivanl