Python3.13の新機能 Python 3.13の新機能(その5)PEP 742: TypeIs による型の絞り込み

PEP 703: フリースレッドモードPEP 696: 型パラメータのデフォルト型PEP 702: 型システムを利用したDeprecate(廃止予定)の指定PEP 705: TypeDict: 読み取り専用アイテムPEP 742: TypeIs による型の絞り込み

Python 3.10では PEP 647: ユーザ定義の型ガード が導入され、型ガード を定義して型推論に利用できるようになりました。

from typing import TypeGuard

def is_int(x: object) -> TypeGuard[int]:
    return isinstance(x, int)

def func(x:object) -> None:
    if is_int(x):
        reveal_type(x) # x は int

型ガードについては、Python 3.10の新機能(その7) ユーザ定義型ガード の解説を参照してください。

型ガードの問題点

型ガード関数は自由度が高く、指定した引数とは無関係な型を指定したり、第一引数のオブジェクト以外の引数を使って型を判定することを認められています。このために発生する不整合を避けるために、以下のルールが定められています。

  1. 型ガードが True を返す場合、その引数の型は常に型ガードで指定した以外の情報を使って推論してはならない。次の例では、ガードしたブロック内では yは常に X としてのみ扱われる。

    def is_x(y: Y) -> TypeGuard[X]:
        return True
    
    y:Y = Y()
    if is_x(y):
        reveal_type(y) # y は X
    
  2. 型ガードが False を返す場合、その情報を推論に使ってはならない。

    def is_black_cat(cat: Cat, color: Color) -> TypeGuard[Cat]:
        return color == Color.Black
    
    cat: Cat = Cat()
    if not is_black_cat(cat):
        reveal_type(cat) # catはCat。 is_black_cat()の結果を推論に利用してはならない
    

しかし、型ガードのリリース後、このようなルールでは不便なケースがあることが明らかになってきました。例えば、次のような場合です。

def is_list(o:object)->TypeGuard[list[Any]]:
    return isinstance(o, list)

a:list[int] = [1]

if is_list(a):
    reveal_type(a) # a は list[Any]

この場合、is_list() の値が Trueなら alist[int] であることは明らかですが、前述のルールのため、list[Any] と推論しなければなりません。

また、次のケースを考えてみましょう。

def is_str(o:int|str)->TypeGuard[str]:
    return isinstance(o, str)

def foo(a:int|str)->None:
    if is_str(a):
        reveal_type(a) # a は str
    else:
        reveal_type(a) # a は int | str

この場合、is_str(a) の結果が False なら、aint であることは明らかです。しかし、is_str(a)False の場合に、その結果を使って a の型を int と推論することは認められていません。

PEP 742: Narrowing types with TypeIs

PEP 742では、このような用途に対応するために、新しく 型の絞り込み を行う TypeIs という形式が導入されました。

from typing import TypeIs

def is_str(o:int|str)->TypeIs[str]:
    return isinstance(o, str)

TypeIsTypeGuard と似ていますが、TypeIs で絞り込む型は、引数の型に互換性を持たなければなりません。また、複数のパラメータを指定する場合、2番目以降の引数を絞り込みに利用してはなりません。

TypeIs では、この条件によって TypeGuard にあった制限を緩和し、次のような絞り込みを行えるようになっています。

def is_list(o:object)->TypeIs[list[Any]]:
    return isinstance(o, list)

a:list[int] = [1]

if is_list(a):
    reveal_type(a) # a は list[int] と推論可能

def is_str(o:int|str)->TypeGuard[str]:
    return isinstance(o, str)

def foo(a:int|str):
    if is_str(a):
        reveal_type(a) # a は str
    else:
        reveal_type(a) # a は int と推論可能

このような絞り込みは、型述語(type predicate) とも呼ばれます。

Copyright © 2001-2023 python.jp Privacy Policy python_japan
Amazon.co.jpアソシエイト
Amazonで他のPython書籍を検索