型修飾子

この章では、いくつかの 型修飾子 の動作について説明します。 追加の型修飾子は他の章で説明されています:

@final

(元々 PEP 591 で指定されていました。)

typing.final デコレータは、継承とオーバーライドの使用を制限するために使用されます。

型チェッカーは、@final でデコレートされたクラスがサブクラス化されることや、@final でデコレートされたメソッドがサブクラスでオーバーライドされることを禁止する必要があります。 メソッドデコレータバージョンは、インスタンスメソッド、クラスメソッド、スタティックメソッド、およびプロパティのすべてで使用できます。

例えば:

from typing import final

@final
class Base:
    ...

class Derived(Base):  # エラー: final クラス "Base" から継承することはできません
    ...

および:

from typing import final

class Base:
    @final
    def foo(self) -> None:
        ...

class Derived(Base):
    def foo(self) -> None:  # エラー: final 属性 "foo" をオーバーライドすることはできません
                            # (ベースクラス "Base" で以前に宣言されました)
        ...

オーバーロードされたメソッドの場合、@final は実装に配置する必要があります (またはスタブの場合は最初のオーバーロードに):

from typing import Any, overload

class Base:
    @overload
    def method(self) -> None: ...
    @overload
    def method(self, arg: int) -> int: ...
    @final
    def method(self, x=None):
        ...

非メソッド関数に @final を使用することはエラーです。

Final

(元々 PEP 591 で指定されていました。)

typing.Final type qualifier は、変数や属性が再割り当て、再定義、またはオーバーライドされないことを示すために使用されます。

構文

Final はいくつかの形式のいずれかで使用できます:

  • 明示的な型を使用して、構文 Final[<type>] を使用します。 例:

    ID: Final[float] = 1
    
  • 型注釈なしで使用します。 例:

    ID: Final = 1
    

    型チェッカーは通常の型推論メカニズムを適用して ID の型を決定する必要があります (ここではおそらく int)。 ジェネリッククラスとは異なり、これは Final[Any] と同じではないことに注意してください。

  • クラス本体およびスタブファイルでは、右辺を省略して ID: Final[float] とだけ書くことができます。 右辺が省略された場合、Final に明示的な型引数が必要です。

  • 最後に、self.id: Final = 1 として (角括弧内に型を指定することもできます)。 これは __init__ メソッド内でのみ許可されており、インスタンスが作成されるときに最初に一度だけ final インスタンス属性が割り当てられるようにします。

セマンティクスと例

final 名を定義するための主なルールは次の 2 つです:

  • モジュールまたはクラスごとに特定の属性に対して 最大 1 つ の final 宣言が存在することができます。 同じ名前のクラスレベルおよびインスタンスレベルの定数を別々に持つことはできません。

  • final 名には 正確に 1 つ の割り当てが必要です。

これは、型チェッカーが型チェックされたコードで final 名へのさらなる割り当てを防ぐ必要があることを意味します:

from typing import Final

RATE: Final = 3000

class Base:
    DEFAULT_ID: Final = 0

RATE = 300  # エラー: final 属性に割り当てることはできません
Base.DEFAULT_ID = 1  # エラー: final 属性をオーバーライドすることはできません

型チェッカーは、ループ内で Final 宣言を許可する必要はありません。 ループの後続の反復で同じ変数に複数の割り当てが行われるためです。

さらに、型チェッカーはサブクラスで final 属性がオーバーライドされるのを防ぐ必要があります:

from typing import Final

class Window:
    BORDER_WIDTH: Final = 2.5
    ...

class ListView(Window):
    BORDER_WIDTH = 3  # エラー: final 属性をオーバーライドすることはできません

クラス本体で初期化子なしで宣言された final 属性は、__init__ メソッドで初期化する必要があります (スタブファイルを除く):

class ImmutablePoint:
    x: Final[int]
    y: Final[int]  # エラー: 初期化子なしの final 属性

    def __init__(self) -> None:
        self.x = 1  # 良い

データクラス の生成された __init__ メソッドはこの要件を満たします: dataclass 本体にある単なる x: Final[int] は許可されます。 生成された __init__x を初期化します。

型チェッカーは、クラス本体で初期化された final 属性をクラス変数として推論する必要があります。 ただし、データクラス の場合を除きます。 ここでは、x: Final[int] = 3 はデフォルト値 3 を持つ dataclass フィールドおよびインスタンスレベルの final 属性 x を作成します。 x: ClassVar[Final[int]] = 3 は、値 3 を持つ final クラス変数を作成するために必要です。 非 dataclass では、ClassVarFinal を組み合わせることは冗長であり、型チェッカーは冗長性に対して警告またはエラーを出すことができます。

Final は割り当てまたは変数注釈でのみ使用できます。 他の位置で使用することはエラーです。 特に、関数引数の注釈には Final を使用できません:

x: list[Final[int]] = []  # エラー!

def fun(x: Final[List[int]]) ->  None:  # エラー!
    ...

Final は他の型修飾子 (例: ClassVar または Annotated) のみでラップすることができます。 型パラメータで使用することはできません (例: list[Final[int]] は許可されていません)。

名前を final として宣言することは、その名前が別の値に再バインドされないことを保証するだけであり、値を不変にするわけではないことに注意してください。 不変の ABC およびコンテナは、Final と組み合わせて使用してそのような値の変更を防ぐことができます:

x: Final = ['a', 'b']
x.append('c')  # OK

y: Final[Sequence[str]] = ['a', 'b']
y.append('x')  # エラー: "Sequence[str]" には属性 "append" がありません
z: Final = ('a', 'b')  # これも動作します

型チェッカーは、リテラルで初期化された final 名の使用を、リテラルに置き換えられたかのように扱う必要があります。 例えば、次のようにする必要があります:

from typing import NamedTuple, Final

X: Final = "x"
Y: Final = "y"
N = NamedTuple("N", [(X, int), (Y, int)])

Annotated

(元々 PEP 593 によって指定されました。)

構文

Annotated基本式 と少なくとも 1 つの Python 値を表す メタデータ でパラメータ化されます:

from typing import Annotated

Annotated[BaseExpr, Metadata1, Metadata2, ...]

構文の具体的な詳細は次のとおりです:

  • 基本式 (Annotated の最初の引数) は、使用されるコンテキストで有効でなければなりません:

    • Annotated が任意の 注釈式 が許可される場所で使用される場合、基本式は注釈式である必要があります。

    • それ以外の場合、基本式は有効な expression でなければなりません。

  • 複数のメタデータ要素がサポートされています (Annotated は可変引数をサポートしています):

    Annotated[int, ValueRange(3, 10), ctype("char")]
    
  • 少なくとも 1 つのメタデータ要素が必要です (Annotated[int] は無効です)

  • メタデータの順序は保持され、等価性チェックにおいて重要です:

    Annotated[int, ValueRange(3, 10), ctype("char")] != Annotated[
        int, ctype("char"), ValueRange(3, 10)
    ]
    
  • ネストされた Annotated 型はフラット化され、メタデータは最も内側の Annotated 式から順に並べられます:

    Annotated[Annotated[int, ValueRange(3, 10)], ctype("char")] == Annotated[
        int, ValueRange(3, 10), ctype("char")
    ]
    
  • 重複したメタデータ要素は削除されません:

    Annotated[int, ValueRange(3, 10)] != Annotated[
        int, ValueRange(3, 10), ValueRange(3, 10)
    ]
    
  • Annotated はネストされたおよびジェネリックエイリアスの定義で使用できますが、structural をラップする場合のみです:

    T = TypeVar("T")
    Vec = Annotated[list[tuple[T, T]], MaxLen(10)]
    V = Vec[int]
    
    V == Annotated[list[tuple[int, int]], MaxLen(10)]
    
  • ほとんどの 特殊形式 と同様に、Annotatedtype または type[T] に割り当てることはできません:

    v1: type[int] = Annotated[int, ""]  # 型エラー
    
    SmallInt: TypeAlias = Annotated[int, ValueRange(0, 100)]
    v2: type[Any] = SmallInt  # 型エラー
    
  • Annotated を呼び出そうとすると (パラメータ化されているかどうかに関係なく)、型チェッカーは型エラーとして扱う必要があります:

    Annotated()  # 型エラー
    Annotated[int, ""](0)  # 型エラー
    
    SmallInt = Annotated[int, ValueRange(0, 100)]
    SmallInt(1)  # 型エラー
    

PEP 593 およびこの仕様の以前のバージョンでは、Annotated の追加引数に対して「注釈」という用語が使用されていました。 「注釈」という用語は、Python 構文の一部であるパラメータ、戻り値、および変数の注釈と混同しないように廃止されました。

意味

Annotated によって提供されるメタデータは、静的解析またはランタイム解析のいずれかに使用できます。 ライブラリ (またはツール) が Annotated[T, x] のインスタンスに遭遇し、メタデータ要素 x に特別なロジックがない場合、それを無視し、式を T と同等として扱う必要があります。 したがって、一般的に、任意の structural または annotation expression は、ラップされた式の意味を変更せずに Annotated でラップできます。 ただし、型チェッカーは特定のメタデータ要素を認識し、それらを使用して標準の型システムへの拡張を実装することを選択できます。

Annotated メタデータは、基本式、注釈されているシンボル、またはプログラムの他の側面のいずれかに適用される場合があります。

メタデータの消費

最終的には、メタデータをどのように解釈するか (もし解釈する場合) は、Annotated 型に遭遇するツールまたはライブラリの責任です。 Annotated 型に遭遇するツールまたはライブラリは、メタデータが興味のあるものであるかどうかを判断するために (例: isinstance() を使用して) メタデータをスキャンできます。

未知のメタデータ: ツールまたはライブラリがメタデータをサポートしていない場合、または未知のメタデータ要素に遭遇した場合、それを無視し、注釈を基本式として扱う必要があります。

メタデータの名前空間: メタデータの名前空間は必要ありません。 メタデータオブジェクトのクラスが名前空間として機能します。

複数のメタデータ要素: クライアントが 1 つの注釈に複数のメタデータ要素を持つことを許可するかどうか、およびそれらの要素をどのようにマージするかは、メタデータを消費するツール次第です。

Annotated 型を使用すると、任意の注釈に同じ (または異なる) 型の複数のメタデータ要素を配置できるため、メタデータを消費するツールまたはライブラリは潜在的な重複を処理する責任があります。 例えば、値範囲解析を行っている場合、次のようにすることができます:

T1 = Annotated[int, ValueRange(-10, 5)]
T2 = Annotated[T1, ValueRange(-20, 3)]

ネストされた注釈をフラット化すると、次のようになります:

T2 = Annotated[int, ValueRange(-10, 5), ValueRange(-20, 3)]

エイリアスと冗長性に関する懸念

typing.Annotated を至る所に書くことは非常に冗長になる可能性があります。 幸いなことに、型のエイリアスを作成する機能により、実際にはクライアントが大量のボイラープレートコードを書く必要はないと予想されます:

type Const[T] = Annotated[T, my_annotations.CONST]

class C:
    def const_method(self, x: Const[list[int]]) -> int:
        ...