ユーザ定義の型ガードが関数により定義可能に
ユーザ定義の関数を型ガードに使うとうまく機能しない場合に、ユーザ定義型ガードを使うことで型ガードのサポートを向上させます。
型ガードとは?
型ガードとは、静的型チェッカが型の絞り込みに利用する条件のことをいいます。型の絞り込み(type narrowing)とは、プログラムの処理から型の情報を取得することです。例えば以下のリストでは、本来であれば型チェックが働いてエラーとなるところを、処理上は問題ないとしてエラーを回避しています。
# Noneを必ず返す関数 def return_none(): return None # xは必ずNoneとなる x: str | None = return_none() (1) # if文が型ガードになりprint文はエラーにならない if type(x) is str: print(x * 10) (2)
(1)で、変数xはstrまたはNoneをとることが定義されていますが、(2)のtype関数とis演算子による条件式によってxがstr型である場合のみprint文が実行されると見なされるので、チェックによってエラーは発生しません。このような条件が型ガードです。型ガードには、他にisinstance関数など型を特定できるようなものが含まれます。
ユーザ定義型ガード
このように便利な型ガードですが、常にうまく働くとは限りません。例えば、条件の判定に独自の関数を使っていたりすると、型ガードがうまく働きません。そこで、Python 3.10ではtyping.TypeGuardによるユーザ定義型ガードを利用できるようになりました。以下のリストでは、本来ならbool型を返す関数の戻り値をTypeGuard型として、型の絞り込みをサポートしています。
from typing import List, TypeGuard (1) # 戻り値がTrueならxはList[str]と見なす関数 def is_string_list(x: List[object]) -> TypeGuard[List[str]]: (2) for a in x: if not isinstance(a, str): return False return True # リストを作成 a:List = ['hello', 1, 'world'] (3) # ユーザ定義型ガード関数を呼び出して型を絞り込む if is_string_list(a): (4) print(','.join(a))
(1)では、Listに加えてユーザ定義型ガードのためのTypeGuardをインポートしています。
(2)のis_string_list関数の定義では、戻り値の型をTypeGuard[List[str]]としています。これが、ユーザ定義型ガードのための記述です。関数の処理内容は、リストの全要素が文字列であるかを調べて返すというものです。これを満たして関数がTrueを返す場合、最初の引数の型がTypeGuardの引数の型であると見なします。
(3)はリストを作成していますが、3つの要素のうち2つ目だけがintになっているので、(4)のユーザ定義型ガードの働きでprint文は静的型チェックでエラーとはなりません。
まとめ
今回は、Python 3.10以降の機能のうち、Union型、パラメータ仕様変数、明示的な型エイリアス、ユーザ定義の型ガードを紹介しました。
次回は、可変長ジェネリクス、Self型、改良されたTypedDict、文字列リテラル型、データクラス変換など、Python 3.11で追加された機能を中心に紹介します。