導入されて以来、Python 3.5のtyping モジュールは、静的型チェッカーやリンターがエラーを正確に予測するのを助ける方法を提供しようとしています。
ランタイム中にPythonがオブジェクトの型を決定する必要があるため、開発者がコード内で何が起こっているかを正確に把握するのは難しいことがあります。
PyCharm IDEなどの外部型チェッカーでも、エラーを正しく予測するのは平均して約50%だけであり、これについてのこの StackOverflowの回答によります。
Pythonはこの問題を緩和しようとし、外部型チェッカーがエラーを特定できるように型ヒント(型注釈)を導入しています。これは、プログラマーがオブジェクトの型をヒントとして指定し、コンパイル時に型チェッカーが正しく機能することを保証する良い方法です。
これにより、Pythonコードは他の読者にとってもはるかに読みやすく、堅牢になります!
注意: これはコンパイル時に実際の型チェックを行いません。指定された型と同じ型の実際のオブジェクトが返されなかった場合、コンパイルエラーは発生しません。not これがなぜ、no コンパイルエラーが発生する理由です。そのため、外部型チェッカー(mypyなど)を使用して型エラーを特定します。
推奨される前提条件
typing
モジュールを効果的に使用するためには、外部の型チェッカー/リンターを使用して静的型の一致をチェックすることを推奨します。Pythonで最も広く使用されている型チェッカーの1つは、mypyですので、この記事の残りを読む前にインストールすることをお勧めします。
すでにPythonでの型チェックの基本をカバーしています。まずこの記事を読んでください。
この記事では、静的型チェッカーとしてmypy
を使用します。これは、次のようにインストールできます:
pip3 install mypy
mypy
を実行して、型が一致するかどうかを確認できます。これは、Pythonコードを「コンパイル」するようなものです。
mypy program.py
エラーをデバッグした後は、通常通りプログラムを実行できます:
python program.py
これで前提条件が整ったので、モジュールのいくつかの機能を試してみましょう。
型ヒント/型注釈
関数について
関数を注釈付けして、その戻り値の型とパラメータの型を指定することができます。
def print_list(a: list) -> None:
print(a)
これにより、タイプチェッカー(私の場合はmypy
)に、print_list()
という関数があり、list
を引数として受け取り、None
を返すことを通知します。
def print_list(a: list) -> None:
print(a)
print_list([1, 2, 3])
print_list(1)
まず、このタイプチェッカーmypy
で実行してみましょう:
vijay@JournalDev:~ $ mypy printlist.py
printlist.py:5: error: Argument 1 to "print_list" has incompatible type "int"; expected "List[Any]"
Found 1 error in 1 file (checked 1 source file)
予想通り、行#5で引数がint
ではなくlist
であるため、エラーが発生します。
変数について
Python 3.6以降、変数の型を注釈付けして、その型を示すこともできます。ただし、関数が戻り値を返す前に変数の型が変わることを望む場合は、これは必須ではありません。
# 'radius'をfloatとして注釈付けする
radius: float = 1.5
# 値を割り当てずに変数を注釈付けすることもできます!
sample: int
# 'area'を返すfloatとして注釈付けする
def area(r: float) -> float:
return 3.1415 * r * r
print(area(radius))
# 関数のすべての注釈を出力する
# '__annotations__'辞書を使用して
print('Dictionary of Annotations for area():', area.__annotations__)
mypyの出力:
vijay@JournalDev: ~ $ mypy find_area.py && python find_area.py
Success: no issues found in 1 source file
7.068375
Dictionary of Annotations for area(): {'r': <class 'float'>, 'return': <class 'float'>}
これは、mypy
を使用する推奨される方法です。まず、タイプの注釈を提供し、その後にタイプチェッカーを使用します。
タイプエイリアス
typing
モジュールは、タイプエイリアスを提供します。これは、エイリアスに型を割り当てることによって定義されます。
from typing import List
# ベクトルは浮動小数点値のリストです
Vector = List[float]
def scale(scalar: float, vector: Vector) -> Vector:
return [scalar * num for num in vector]
a = scale(scalar=2.0, vector=[1.0, 2.0, 3.0])
print(a)
出力
vijay@JournalDev: ~ $ mypy vector_scale.py && python vector_scale.py
Success: no issues found in 1 source file
[2.0, 4.0, 6.0]
上記のスニペットでは、Vector
はリストの浮動小数点値を表すエイリアスです。上記のプログラムで行っているのは、エイリアスに型ヒントを付けることです。
許容されるエイリアスの完全なリストは、こちらに記載されています。
もう1つの例を見てみましょう。この例では、辞書内のすべてのキー:値ペアをチェックし、それらがname:email形式と一致するかどうかを確認します。
from typing import Dict
import re
# 'ContactDict'というエイリアスを作成します
ContactDict = Dict[str, str]
def check_if_valid(contacts: ContactDict) -> bool:
for name, email in contacts.items():
# nameとemailが文字列であるかどうかを確認します
if (not isinstance(name, str)) or (not isinstance(email, str)):
return False
# emailが[email protected]形式であるかどうかを確認します
if not re.match(r"[a-zA-Z0-9\._\+-]+@[a-zA-Z0-9\._-]+\.[a-zA-Z]+$", email):
return False
return True
print(check_if_valid({'vijay': '[email protected]'}))
print(check_if_valid({'vijay': '[email protected]', 123: '[email protected]'}))
mypyからの出力
vijay@JournalDev:~ $ mypy validcontacts.py
validcontacts.py:19: error: Dict entry 1 has incompatible type "int": "str"; expected "str": "str"
Found 1 error in 1 file (checked 1 source file)
ここでは、2番目の辞書のname
パラメータが整数(123)であるため、mypy
で静的なコンパイル時エラーが発生します。したがって、エイリアスはmypy
からの正確な型チェックを強制する別の方法です。
NewType()を使用してユーザー定義データ型を作成する
NewType()
関数を使用して、新しいユーザー定義型を作成できます。
from typing import NewType
# 'StudentID'という新しいユーザータイプを作成する
# 整数から構成されている
StudentID = NewType('StudentID', int)
sample_id = StudentID(100)
静的型チェッカーは、新しい型を元の型のサブクラスのように扱います。これは論理エラーをキャッチするのに役立ちます。
from typing import NewType
# 'StudentID'という新しいユーザータイプを作成する
StudentID = NewType('StudentID', int)
def get_student_name(stud_id: StudentID) -> str:
return str(input(f'Enter username for ID #{stud_id}:\n'))
stud_a = get_student_name(StudentID(100))
print(stud_a)
# これは間違っています!
stud_b = get_student_name(-1)
print(stud_b)
出力mypyから
vijay@JournalDev:~ $ mypy studentnames.py
studentnames.py:13: error: Argument 1 to "get_student_name" has incompatible type "int"; expected "StudentID"
Found 1 error in 1 file (checked 1 source file)
Any型
これは特別な型で、静的型チェッカー(私の場合はmypy
)に、すべての型がこのキーワードと互換性があると伝えます。
古いprint_list()
関数を考えてみてください。これは今やどんな型の引数でも受け入れるようになりました。
from typing import Any
def print_list(a: Any) -> None:
print(a)
print_list([1, 2, 3])
print_list(1)
これで、mypy
を実行する際にエラーが発生しなくなります。
vijay@JournalDev:~ $ mypy printlist.py && python printlist.py
Success: no issues found in 1 source file
[1, 2, 3]
1
すべての戻り型やパラメータ型が指定されていない関数は、暗黙的にAny
を使用するようになります。
def foo(bar):
return bar
# 静的型チェッカーは上記を同じシグネチャとして扱います
# と見なします:
def foo(bar: Any) -> Any:
return bar
したがって、Any を使用して静的および動的に型付けされたコードを混在させることができます。
結論
この記事では、Pythonのtypingモジュールについて学びました。これは型チェックの文脈で非常に役立ち、mypy
などの外部型チェッカーが正確にエラーを報告できるようにします。
これにより、Pythonの動的型付け言語であるにもかかわらず、静的に型付けされたコードを書く方法が提供されます。
参考文献
- typingモジュールのPythonドキュメント(このモジュールの他のメソッドに関する詳細な情報が含まれており、これを補足的な参照としてお勧めします)
- 型ヒントに関するStackOverflowの質問(これはこのトピックに関する非常に良い議論を提供しています。これも読むことを強くお勧めします!)
Source:
https://www.digitalocean.com/community/tutorials/python-typing-module