🦢

Type hints cheat sheet の日本語訳

2022/03/24に公開

Type hint cheat sheet (Python 3)

このドキュメントは、PEP 484のタイプアノテーション表記を Python3 どのように表現しているかを簡単に説明するチートシートです。

変数 (Variable)

Python 3.6 では PEP 526で変数の注釈をつける構文が導入され、ほとんどの例で変数の型注釈を利用して説明しています。

# Python 3.6 で変数の型を宣言する方法は次のとおりです。
age: int = 1

# Python 3.6 以前では、代わりに型コメントを使用することができます。
age = 1 # type: int

# アノテーションを付けるために、変数を初期化する必要はありません
a: int # ok (割り当てられるまで、実行時の値はありません)

# bool型 以外を使用して初期化するとエラーが発生します
child: bool
if age < 18:
    child = True
else:
    child = False

Pythonの組み込み型を利用したアノテーション (Built-in Type)

from typing import List, Set, Dict, Tuple, Optional

# 単純な組み込み型の場合は、単位型の名前を利用します
x: int = 1
x: float = 1.0
x: bool = True
x: str = "test"
x: bytes = b"test"

# コレクションの場合は、コレクションアイテムのタイプは括弧にないに記述します。
# (Python 3.9+)
x: list[int] = [1]
x: set[int] = {6, 7}

# Python3.8以前の場合は、`typing`モジュール内の大文字で書かれたコレクション(listやtuple,set)タイプを利用します。
x: List[int] = [1]
x: Set[int] = {6, 7}

# Python3.5以前の場合は、型コメント構文です。
x = [1]  # type: List[int]

# マッピング(dict:辞書)には、キーと値の両方の型が必要です。
x: dict[str, float] = {"field": 2.0}  # Python 3.9+
x: Dict[str, float] = {"field": 2.0}

# 固定サイズのタプルの場合は、すべての要素の方を指定します。
x: tuple[int, str, float] = (3, "yes", 7.5)  # Python 3.9+
x: Tuple[int, str, float] = (3, "yes", 7.5)

# 可変サイズのタプルの場合は、1つの方と省略記号(...=Ellipsis)を利用する。
x: tuple[int, ...] = (1, 2, 3)  # Python 3.9+
x: Tuple[int, ...] = (1, 2, 3)

# Noneになる可能性のある値には`Optional[]`を使用する
x: str | None = some_function() # Python 3.10+
x: Optional[str] = some_function()

# Mypyは 以下のif文(if-statement)中でxがNoneならないことを理解できます。
# Mypyを利用する場合は、x.upperのようなメソッド(を利用)がエラーにならないようにするには
# 一度 if文を利用して None でないことを確認する必要があります。
if x is not None:
    print(x.upper())

# ある不変条件によって値がNoneにならない場合は、assertを使用します。
assert x is not None
print(x.upper())

Functions

Python 3では、関数宣言のアノテーション構文がサポートされています。

from typing import Callable, Iterator, Union, Optional

# 関数定義の型アノテーションは以下のように行います。
def stringify(num: int) -> str:
    return str(num)

# 複数の引数を指定する方法は以下の通りです。
def plus(num1: int, num2: int) -> int:
    return num1 + num2

# タイプアノテーションの後に引数のデフォルト値を追加する
def f(num1: int, my_float: float = 3.5) -> float:
    return num1 + my_float

# 呼び出し可能(関数)値へのアノテーションは以下のように行います。
# 関数の引数と戻り値の型定義をまとめて記述するためのものです。
# コールバック関数のように関数を引数に取るような場合に利用します。
x: Callable[[int, float], float] = f

# int型を返すジェネレータ関数は、以下のようなアノテーションを行います。
def g(n: int) -> Iterator[int]:
    i = 0
    while i < n:
        yield i
        i += 1

# 関数アノテーションは複数行に分割して記述することもできます。
def send_email(address: Union[str, list[str]],
               sender: str,
               cc: Optional[list[str]],
               bcc: Optional[list[str]],
               subject='',
               body: Optional[list[str]] = None
               ) -> bool:
    ...

# 位置専用引数(Positional-only argument)
# 引数にアンダースコアを2つつけると、位置のみの宣言ができます。
def quux(__x: int) -> None:
    pass

quux(3)      # Ok
quux(__x=3)  # Error


# キーワード専用引数(Keyword-only argment)
# 引数に * がある場合は、それ以降に定義していある引数は必ずキーワード引数として呼び出す必要があります。
def konly(a: int, b: int, * , c: int = 1, d: int = 2):
    return a + c + b + d
    
konly(1,2,c=3) # Ok
konly(1,2,3)   # Error

複雑になった場合

from typing import Union, Any, Optional, cast

# mypy が式のどこでどのような型を推定しているかを知るには
# あなたのプログラムでは、それを reveal_type() で囲みます。 Mypy はエラーを表示します。
# コードを実行する前にもう一度取り除いてください。
reveal_type(1)  # -> Revealed type is "builtins.int"

# 複数の型を持つ場合
x: list[Union[int, str]] = [3, 5, "test", "fun"]
x: list[str | None] = [3, 5, "test", "fun"] # Python 3.10+


# 型がわからない、または型がありすぎる場合はAnyを使う
# Anyはなるべく使わない。Anyを使うと型ヒントを利用するメリットを享受できなくなります。
# ですが、まだPythonで型定義を行うようになったのが最近のためすべてのモジュールが対応している訳ではないため、一時しのぎ程度で利用してください。
x: Any = mystery_function()

# 空のリストまたはNoneで変数を初期化した場合、
# タイプアノテーションを提供することで、 mypy に型を伝える必要があります。
x: list[str] = []
x: Optional[str] = None

# 各位置引数およびキーワード引数の型アノテーション
# 以下はすべての位置引数およびキーワード引数を str 型で定義しています。
def call(self, *args: str, **kwargs: str) -> str:
    request = make_request(*args, **kwargs)
    return self.do_api_query(request)

# 指定された行のエラーを抑制するために "type: ignore" のコメントを使用します。
# mypyのバグに遭遇したときなどに利用します。
# グッドプラクティスはすべての ignore にバグのリンクをコメントすることです。
x = confusing_function()  # type: ignore  # https://github.com/python/mypy/issues/1167

# castは推測されたものを上書きするためのヘルパー関数です。
# これは mypy のみのために利用します。実行時のチェックはありません。
a = [4]
b = cast(list[int], a)  # Passes fine
c = cast(list[str], a)  # Passes fine (no runtime check)
reveal_type(c)  # -> Revealed type is "builtins.list[builtins.str]"
print(c)  # -> [4]; the object is not cast

# クラスに動的な属性を持たせたい場合は、 "__setattr__"をオーバーライドするようにします。
# または スタブやソースコードで "__getattr__" を記述してください。
#
# "__setattr__" は、名前への動的な割り当てを可能にします。
# "__getattr__" は名前にダイナミックにアクセスできるようにする。
class A:
    # これにより、 xが"value"と同じ型であれば、任意のA.xへの代入が可能になる
    # (任意の型を許可するために "value: Any" を使用します)
    def __setattr__(self, name: str, value: int) -> None: ...

    # これにより、xが戻り値の型と互換性がある場合、任意のA.xにアクセスすることができるようになります。
    def __getattr__(self, name: str) -> int: ...

a.foo = 42          # Ok
a.bar = 'Ex-parrot' # Fails type checking (return type is int)

standard "duck types"

一般的なPythonのコードでは、ListやDict(辞書)を引数に取る関数の多くは、引数が何らかの形で「リスト的」または「ディクト的」であればよいことになっています。「リストライク」または「ディクショナリク」(あるいはそれ以外の何か)の特定の意味を「ダック型」と呼び、慣用的なPythonでよく使われるいくつかのダック型は標準化されています。

引用

ダック・タイピング(英: duck typing)とは、Smalltalk、Perl、PHP、Python、Ruby、JavaScriptなどの動的型付けに対応したオブジェクト指向プログラミング言語に特徴的な、型付けのスタイル(作法)のひとつである。

出展: ダック・タイピング

from typing import Mapping, MutableMapping, Sequence, Iterable

# 一般的なイテラブル(forで使えるもの)にはIterableを使用します。
# Sequenceは "len " や "__getitem__" が必要な場合は利用します。
# MutableSequenceは"__setitem__"を使って変異させる可能性があるものの時に利用する。
def f(ints: Iterable[int]) -> list[str]:
    ints[0]     # Iterableの場合は Error
    ints[0] = 2 # IterableまたはSequenceの場合はError
    return [str(x) for x in ints]

f(range(1, 3))

# Mapping はディクショナリー的なオブジェクトを記述するもので、"__getitem__" と共に使用します。
def f(my_mapping: Mapping[int, str]) -> list[int]:
    my_mapping[5] = 'maybe'  # これを試すと、mypyがエラーを投げます
    return list(my_mapping.keys())

f({3: 'yes', 4: 'no'})

# MutableMappingは"__setitem__"を使って変異させる可能性があるものです。
def f(my_mapping: MutableMapping[int, str]) -> set[str]:
    my_mapping[5] = 'maybe'  # ...but mypy is OK with this.
    return set(my_mapping.values())

f({3: 'yes', 4: 'no'})

Protocolsとstructural subtypingを使って、独自のダックタイプを作ることも可能です。

Classes

class MyClass:
    # オプションで、クラス本体でインスタンス変数を宣言することができます。
    attr: int
    # デフォルト値を持つインスタンス変数である
    charge_percent: int = 100

    # "__init__" メソッドは何も返さないので、何も返さない他のメソッドと同じように "None" 型を返します。
    def __init__(self) -> None:
        ...

    # インスタンスメソッドの場合、"self "の型は省略する。
    def my_method(self, num: int, str1: str) -> str:
        return num * str1

# ユーザー定義クラスはアノテーションの型として有効です
x: MyClass = MyClass()

# ClassVarアノテーションを使用すると、クラス変数を宣言することができます。
class Car:
    seats: ClassVar[int] = 4
    passengers: ClassVar[list[str]]

# また、"__init__"で属性の型を宣言することができます。
class Box:
    def __init__(self) -> None:
        self.items: list[str] = []

Coroutines and asyncio

コルーチンと非同期コードの型付けの詳細については、async/awaitの型付けを参照してください。

import asyncio

# A coroutine is typed like a normal function
async def countdown35(tag: str, count: int) -> str:
    while count > 0:
        print('T-minus {} ({})'.format(count, tag))
        await asyncio.sleep(0.1)
        count -= 1
    return "Blastoff!"

その他

import sys
import re
from typing import Match, AnyStr, IO

# "typing.Match" はreモジュールの正規表現マッチについて説明しています。
x: Match[str] = re.match(r'[0-9]+', "15")

# open()呼び出しから来る任意のオブジェクトを受け取るか返すべき関数には 
# IO[] を使用する (IO[] は読み込み、書き込み、その他のモードの区別をしない)
def get_sys_IO(mode: str = 'w') -> IO[str]:
    if mode == 'w':
        return sys.stdout
    elif mode == 'r':
        return sys.stdin
    else:
        return sys.stdout

# 前方参照は、クラスが定義される前に参照したい場合に便利です。
def f(foo: A) -> int:  # This will fail
    ...

class A:
    ...

# 文字列リテラル 'A' を使用した場合、ファイルの後半にその名前のクラスが存在する限り、パスします
def f(foo: 'A') -> int:  # Ok
    ...

Decorators

デコレータ機能はジェネリックスで表現することができます。詳細は デコレータの宣言 を参照してください。

from typing import Any, Callable, TypeVar

F = TypeVar('F', bound=Callable[..., Any])

def bare_decorator(func: F) -> F:
    ...

def decorator_args(url: str) -> Callable[[F], F]:
    ...

型ヒントの入門記事を書きました。

https://zenn.dev/mook_jp/articles/35ced19b2ae40f

Discussion