【Python】型ヒント(Type Hints)の書き方入門

Python

こんにちは、かつコーチです。

チームで開発しているコードに、def greet(name: str) -> str:のような記述を見かけて、「これは何のためにあるのか」と気になったことはないでしょうか。

「型ヒントを書いても、実行結果は変わらないのでは」

Optionallist[int]の書き方が分からない」

そう感じている方も多いと思います。

この記事では、型ヒントの基本構文、OptionalUnionlist[int]などの書き方、そしてmypyとの組み合わせ方までを解説します。

読み終える頃には、型ヒント付きのコードを読み書きできるようになります。

型ヒントの基本構文

型ヒントとは何か

型ヒントとは、変数や関数の引数・返り値に、想定している型を注釈として書く仕組みです。

Pythonは元々、変数の型を明示しなくても動く動的型付け言語(実行時に型が決まる言語)です。

型ヒントを書いても、Python自体の実行結果が変わることはありません。

あくまで人間や、後述するmypyのようなツールに向けた「注釈」という位置づけです。

def greet(name: str) -> str:
    return f"こんにちは、{name}さん"


message = greet("かつコーチ")
print(message)

実行結果は次の通りです。

こんにちは、かつコーチさん

name: strは「nameは文字列を想定している」、-> strは「返り値は文字列を想定している」という意味です。

変数への型ヒント

関数の引数・返り値だけでなく、変数にも型ヒントをつけられます。

age: int = 30
price: float = 1980.0
is_active: bool = True

print(age, price, is_active)

実行結果は次の通りです。

30 1980.0 True

型ヒントをつけていても、実際には違う型の値を代入することが、Python自体としては可能です。

age: int = 30
age = "三十歳"  # 型ヒントと違う型を代入してもエラーにはならない

print(age)

実行結果は次の通りです。

三十歳

このように、型ヒントだけでは間違いを実行時に防げません。

チェックには後述するmypyのようなツールが必要になります。

Optional・Union・list[int]等の書き方

list[int]で要素の型を指定する

リストや辞書など、要素を複数持つデータ構造については、[]を使って要素の型まで指定できます。

def total_price(prices: list[int]) -> int:
    return sum(prices)


result = total_price([100, 200, 300])
print(result)

実行結果は次の通りです。

600

list[int]は「intの要素が並んだリスト」という意味です。

辞書であればdict[str, int]のように、キーと値それぞれの型を指定します。

def show_scores(scores: dict[str, int]) -> None:
    for name, score in scores.items():
        print(f"{name}: {score}点")


show_scores({"田中": 80, "佐藤": 95})

実行結果は次の通りです。

田中: 80点
佐藤: 95点

返り値がない関数には、-> Noneを指定するのが慣例です。

Optionalで「Noneの可能性がある」ことを示す

Optionalは、「その値がNoneかもしれない」ことを示す型ヒントです。

標準ライブラリのtypingモジュールからインポートして使います。

from typing import Optional


def find_user(user_id: int) -> Optional[str]:
    users = {1: "田中", 2: "佐藤"}
    return users.get(user_id)


print(find_user(1))
print(find_user(99))

実行結果は次の通りです。

田中
None

Optional[str]は、「str型か、またはNoneのどちらか」を意味します。

Python 3.10以降では、Optional[str]の代わりにstr | Noneという書き方も使えます。

def find_user(user_id: int) -> str | None:
    users = {1: "田中", 2: "佐藤"}
    return users.get(user_id)

どちらも意味は同じですが、Python 3.10以降のプロジェクトでは、str | Noneの方が簡潔で好まれる傾向にあります。

Unionで複数の型を許容する

Unionは、「複数の型のうちいずれか」を示す型ヒントです。

from typing import Union


def format_id(value: Union[int, str]) -> str:
    return f"ID-{value}"


print(format_id(123))
print(format_id("abc"))

実行結果は次の通りです。

ID-123
ID-abc

Union[int, str]は、「intまたはstrのどちらか」を意味します。

こちらもPython 3.10以降では、int | strという書き方に置き換えられます。

def format_id(value: int | str) -> str:
    return f"ID-{value}"

mypyへの軽い言及

mypyで型の矛盾をチェックする

mypyは、型ヒントをもとに、コードの中に型の矛盾がないかをチェックしてくれる静的型チェッカー(実行せずにコードを解析するツール)です。

インストールは次のコマンドで行います。

pip install mypy

型ヒントと矛盾するコードを用意し、mypyでチェックしてみます。

# sample.py
def add(a: int, b: int) -> int:
    return a + b


result = add(1, "2")
print(result)
mypy sample.py

実行結果は次のようになります。

sample.py:5: error: Argument 2 to "add" has incompatible type "str"; expected "int"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

add関数はintを2つ受け取る想定なのに、"2"という文字列を渡してしまっている矛盾を、実行前に検出できています。

このように、型ヒントを書いておくと、mypyと組み合わせることで、実行前にバグの芽を見つけやすくなります。

チーム開発でCI(継続的インテグレーション)にmypyを組み込んでおくと、型の不整合を機械的に防げるようになります。

よくあるつまずきポイント・エラー対処

引数の初期値にNoneを使うと矛盾が起きやすい

筆者が型ヒントを書き始めたばかりの頃、デフォルト引数にNoneを使いつつ、型ヒントをlist[int]のままにしてしまい、mypyで警告を受けた経験があります。

❌ Before(Noneを許容していない型ヒント)

def add_item(item: int, items: list[int] = None) -> list[int]:
    if items is None:
        items = []
    items.append(item)
    return items


print(add_item(1))

mypyでチェックすると、次のようなエラーメッセージが表示されます。

sample.py:1: error: Incompatible default for argument "items" (default has type "None", argument has type "list[int]")  [assignment]

型ヒント上はitemslist[int]のはずなのに、デフォルト値としてNoneを渡しているという矛盾が指摘されています。

Pythonの実行自体は問題なく通ってしまうため、mypyを使わないと気づきにくいミスです。

✅ After(Optionalで矛盾を解消した書き方)

from typing import Optional


def add_item(item: int, items: Optional[list[int]] = None) -> list[int]:
    if items is None:
        items = []
    items.append(item)
    return items


print(add_item(1))

実行結果は次の通りです。

[1]

itemsの型ヒントをOptional[list[int]]list[int] | Noneでも可)に変更したことで、「Noneが入りうる」ことが正しく表現され、mypyのエラーも解消されます。

「デフォルト値にNoneを使う引数は、型ヒントもOptionalにする」という組み合わせは、頻出パターンとして覚えておくと役立ちます。

まとめ

この記事のポイント

  • 型ヒントは変数・引数・返り値に想定する型を注釈として書く仕組みで、実行結果自体は変えない
  • list[int]dict[str, int]のように、要素の型まで指定できる
  • Optional[型]は「その型かNone」、Union[型, 型]は「複数の型のいずれか」を示す
  • Python 3.10以降では型 | None型 | 型という書き方も使える
  • mypyを使うと、型ヒントをもとにした矛盾を実行前に検出できる

次に読むべき記事

  • 関数の定義方法の基本
  • クラスとオブジェクト指向の基本
  • コンテキストマネージャ(with文)の仕組みと自作方法

タグ: Python, 中級者向け, 基本文法

タイトルとURLをコピーしました