はじめに
マグダレナ・トムジークによるイラスト
Pythonは動的型付けを備えた言語であり、さまざまな型の変数を自由に操作できます。 ただし、何らかの方法でコードを記述する場合、使用される変数のタイプを想定します(これは、アルゴリズムまたはビジネスロジックの制限が原因である可能性があります)。 そして、プログラムが正しく動作するためには、間違ったタイプのデータの転送に関連するエラーをできるだけ早く見つけることが重要です。
Pythonの最新バージョン(3.6以降)での動的なカモタイピングの概念を維持し、変数タイプ、クラスフィールド、引数、および関数の戻り値の注釈をサポートします。
型注釈は、Pythonインタープリターによって単に読み取られ、処理されなくなりますが、サードパーティのコードから使用でき、主に静的アナライザーで使用するために設計されています。
私の名前はアンドレイ・ティホノフで、ラモダでバックエンド開発に従事しています。
この記事では、型注釈の使用の基本を説明し、注釈をtyping
実装される典型的な例を検討しtyping
。
注釈ツール
型注釈は、入力時に誤ったコードを強調表示したり、ヒントを提供したりする多くのPython IDEでサポートされています。
たとえば、Pycharmでは次のようになります。
エラーの強調表示
ヒント:
タイプ注釈もコンソールリンターによって処理されます。
pylintの出力は次のとおりです。
$ pylint example.py ************* Module example example.py:7:6: E1101: Instance of 'int' has no 'startswith' member (no-member)
しかし、mypyが見つけた同じファイルの場合:
$ mypy example.py example.py:7: error: "int" has no attribute "startswith" example.py:10: error: Unsupported operand types for // ("str" and "int")
異なるアナライザーの動作は異なる場合があります。 たとえば、mypyとpycharmは変数タイプの変更を別々に処理します。 さらに例では、mypyの出力に焦点を当てます。
いくつかの例では、起動時に例外なくコードが実行される場合がありますが、間違ったタイプの変数の使用による論理エラーが含まれる場合があります。 また、一部の例では、実行されないこともあります。
基本
Pythonの古いバージョンとは異なり、型注釈はコメントやdocstringではなく、直接コードで記述されます。 一方で、これは下位互換性を壊しますが、一方で、それは明らかにそれがコードの一部であり、それに応じて処理できることを意味します
最も単純な場合、注釈には直接予想されるタイプが含まれます。 より複雑なケースについては、以下で説明します。 基本クラスが注釈として指定されている場合、その子孫のインスタンスを値として渡すことができます。 ただし、基本クラスに実装されている機能のみを使用できます。
変数の注釈は、識別子の後のコロンの後に書き込まれます。 この後、値を初期化できます。 例えば
price: int = 5 title: str
関数パラメーターには変数と同じ方法で注釈が付けられ、戻り値は矢印->
後、最後のコロンの前に示されます。 例えば
def indent_right(s: str, width: int) -> str: return " " * (max(0, width - len(s))) + s
クラスフィールドの場合、クラスを定義するときに注釈を明示的に指定する必要があります。 ただし、アナライザーは__init__
メソッドに基づいてそれらを自動的に出力できますが、この場合、ランタイムでは使用できません。 記事の第2部で、実行時の注釈の操作の詳細
class Book: title: str author: str def __init__(self, title: str, author: str) -> None: self.title = title self.author = author b: Book = Book(title='Fahrenheit 451', author='Bradbury')
ところで、dataclassを使用する場合、フィールドタイプはクラスで指定する必要があります。 データクラスの詳細
組み込み型
標準タイプを注釈として使用できますが、多くの便利なものがtyping
モジュールに隠されていtyping
。
オプショナル
変数をint
型でマークし、 None
を割り当てようとすると、エラーが発生します。
Incompatible types in assignment (expression has type "None", variable has type "int")
このような場合、特定のタイプのOptional
注釈がタイピングモジュールで提供されます。 オプション変数のタイプは角括弧で示されていることに注意してください。
from typing import Optional amount: int amount = None # Incompatible types in assignment (expression has type "None", variable has type "int") price: Optional[int] price = None
どれでも
変数の可能なタイプを制限したくない場合があります。 たとえば、これが本当に重要でない場合、または異なるタイプの処理を自分で行う予定がある場合。 この場合、 Any
注釈を使用できます。 Mypyは次のコードを誓うことはありません。
unknown_item: Any = 1 print(unknown_item) print(unknown_item.startswith("hello")) print(unknown_item // 0)
疑問が生じるかもしれません、なぜobject
使用しないのですか? ただし、この場合、どのオブジェクトも転送できますが、 object
インスタンスとしてのみアクセスできると想定されていobject
。
unknown_object: object print(unknown_object) print(unknown_object.startswith("hello")) # error: "object" has no attribute "startswith" print(unknown_object // 0) # error: Unsupported operand types for // ("object" and "int")
連合
型だけでなく一部のみの使用を許可する必要がある場合は、角括弧で囲まれた型のリストでtyping.Union
アノテーションを使用できます。
def hundreds(x: Union[int, float]) -> int: return (int(x) // 100) % 10 hundreds(100.0) hundreds(100) hundreds("100") # Argument 1 to "hundreds" has incompatible type "str"; expected "Union[int, float]"
ところで、 Optional[T]
アノテーションはUnion[T, None]
と同等ですが、そのようなエントリは推奨されません。
コレクション
型注釈メカニズムは、コンテナーに格納されている要素のタイプを指定できるようにするジェネリックメカニズム( Generics 、記事の後半部分)をサポートしています。
リスト
変数にリストが含まれていることを示すために、注釈としてリストタイプを使用できます。 ただし、リストに含まれる要素を指定する場合、そのような注釈は適切ではなくなります。 これにはtyping.List
があります。 オプションの変数のタイプを示したのと同じ方法で、リスト項目のタイプを角括弧で示します。
titles: List[str] = ["hello", "world"] titles.append(100500) # Argument 1 to "append" of "list" has incompatible type "int"; expected "str" titles = ["hello", 1] # List item 1 has incompatible type "int"; expected "str" items: List = ["hello", 1]
リストには、同じタイプの要素が無制限に含まれていると想定されています。 ただし、要素の注釈に制限はありませんAny
、 Optional
、 List
を使用できます。 項目タイプが指定されていない場合、 Any
と見なされます。
リストに加えて、セット用の同様の注釈、 typing.Set
とtyping.FrozenSet
ます。
タプル
リストとは異なり、タプルは異種の要素によく使用されます。 構文は1つの違いを除いて類似しています。タプルの各要素のタイプは、角括弧で個別に示されます。
リストに似たタプルを使用する予定の場合:同じタイプの要素の不明な数を保存する場合、省略記号( ...
)を使用できます。
要素タイプを指定しない注釈Tuple
と同様に機能しTuple[Any, ...]
price_container: Tuple[int] = (1,) price_container = ("hello") # Incompatible types in assignment (expression has type "str", variable has type "Tuple[int]") price_container = (1, 2) # Incompatible types in assignment (expression has type "Tuple[int, int]", variable has type "Tuple[int]") price_with_title: Tuple[int, str] = (1, "hello") prices: Tuple[int, ...] = (1, 2) prices = (1, ) prices = (1, "str") # Incompatible types in assignment (expression has type "Tuple[int, str]", variable has type "Tuple[int, ...]") something: Tuple = (1, 2, "hello")
辞書
辞書の場合、 typing.Dict
使用されます。 キータイプと値タイプには別々に注釈が付けられます。
book_authors: Dict[str, str] = {"Fahrenheit 451": "Bradbury"} book_authors["1984"] = 0 # Incompatible types in assignment (expression has type "int", target has type "str") book_authors[1984] = "Orwell" # Invalid index type "int" for "Dict[str, str]"; expected type "str"
同様にtyping.DefaultDict
とtyping.OrderedDict
使用しtyping.OrderedDict
機能結果
関数の結果のタイプを示すために、任意の注釈を使用できます。 しかし、いくつかの特別なケースがあります。
関数が何も返さない場合(たとえばprint
)、その結果は常にNone
です。 注釈には、 None
も使用します。
このような関数を完了するための有効なオプションは、明示的にNone
を返し、値を指定せずに戻り、returnを呼び出さずに終了します。
def nothing(a: int) -> None: if a == 1: return elif a == 2: return None elif a == 3: return "" # No return value expected else: pass
関数が返されない場合(たとえばsys.exit
ように)、 NoReturn
アノテーションを使用する必要があります。
def forever() -> NoReturn: while True: pass
ジェネレーター関数の場合、つまり、その本体にyield
が含まれる場合、 Iterable[T]
またはGenerator[YT, ST, RT]
アノテーションを使用できます。
def generate_two() -> Iterable[int]: yield 1 yield "2" # Incompatible types in "yield" (actual type "str", expected type "int")
結論の代わりに
型付けモジュールの多くの状況には適切な型がありますが、動作は考慮されているものと似ているため、すべてを検討するわけではありません。
たとえば、 collections.abc.Iterator
のジェネリックバージョンとしてIterator
があり、オブジェクトが__int__
メソッドをサポートしていることを示すために__int__
、または__call__
メソッドをサポートする関数とオブジェクトのCallable
があります
標準では、静的アナライザーの情報のみを含むコメントおよびスタブファイルの形式で注釈の形式も定義しています。
次の記事では、ジェネリックの動作メカニズムとランタイムでの注釈の処理について詳しく説明します。