Pythonの型ヒント(型アノテーション)のすすめ

Python

はじめに

Pythonは、変数や関数の引数に型を書かなくても動く言語です。例えば、次のような関数は普通に書けます。

def calculate_total(items):
    return sum(items)

ただ、この関数をあとから読む人には、items に何を渡せばよいのかがすぐには分かりません。

  • 整数のリストなのか
  • 小数のリストなのか
  • 文字列のリストでもよいのか
  • 戻り値は int なのか float なのか

小さなコードなら問題になりにくいですが、関数が増えてくると、この「読めばたぶん分かる」が少しずつ負担になります。そこで使えるのが 型ヒント(型アノテーション) です。

この記事では、Python 3.10以降を前提に、str | Nonelist[int] のような書き方を使います。古いPythonでは、typing.Optionaltyping.List を使う書き方が必要になる場合があります。

型ヒントを書くと、同じ関数を次のように表せます。

def calculate_total(items: list[int]) -> int:
    return sum(items)

これだけで、items は整数のリストで、戻り値は整数だと分かります。型ヒントは、Pythonの実行速度を速くするためのものではありません。主な目的は、コードを読む人と、エディタや静的解析ツールに意図を伝えることです。

型ヒントで何が嬉しいのか

型ヒントの良さは、実行する前に「このコードは少し怪しい」と気づきやすくなることです。例えば、先ほどの関数に文字列のリストを渡してしまったとします。

def calculate_total(items: list[int]) -> int:
    return sum(items)

result = calculate_total(["100", "200", "300"])

このコードは、list[int] と書いているのに、実際には list[str] を渡しています。Python自体は、型ヒントだけを見て実行前に止めてくれるわけではありません。しかし、mypyやPyrightのような静的解析ツール、あるいはVS Codeなどのエディタは、この不一致を警告できます。つまり型ヒントは、バグをゼロにする魔法ではなく、間違いに早く気づくための目印です。

関数の引数と戻り値に型を書く

最もよく使うのは、関数の引数と戻り値への型ヒントです。

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

この例では、namestr、戻り値も str です。書き方は次のようになります。

def 関数名(引数名: 引数の型) -> 戻り値の型:
    ...

複数の引数がある場合も同じです。

def add_tax(price: int, tax_rate: float) -> int:
    return int(price * (1 + tax_rate))

price は整数、tax_rate は小数、戻り値は整数だと分かります。このように型を書いておくと、関数を使う側も迷いにくくなります。

listやdictの型を書く

リストや辞書も型ヒントで表せます。

numbers: list[int] = [1, 2, 3]
names: list[str] = ["Alice", "Bob", "Charlie"]
scores: dict[str, int] = {"Alice": 90, "Bob": 85}

list[int] は「整数のリスト」、dict[str, int] は「キーが文字列で、値が整数の辞書」という意味です。関数の引数にも使えます。

def average(scores: list[int]) -> float:
    return sum(scores) / len(scores)

この関数は、整数のリストを受け取り、平均値を小数として返します。

Noneを返す可能性がある場合

Pythonでは、値が見つからなかったときに None を返す関数を書くことがあります。例えば、ユーザーIDから名前を取得する関数を考えます。

def get_user_name(user_id: int) -> str | None:
    if user_id < 0:
        return None
    return f"User{user_id}"

str | None は、「文字列かもしれないし、Noneかもしれない」という意味です。このように書いておくと、使う側は None の可能性を意識できます。

name = get_user_name(-1)

if name is not None:
    print(name.upper())

もし None の可能性を考えずに次のように書くと、実行時にエラーになる可能性があります。

name = get_user_name(-1)
print(name.upper())

型ヒントは、こうした「たまにだけ起きるエラー」を見つけやすくしてくれます。なお、古い書き方では Optional[str] もよく使われます。

from typing import Optional

def get_user_name(user_id: int) -> Optional[str]:
    ...

Optional[str]str | None と同じ意味です。最近のPythonでは、str | None の方が読みやすい場面が多いです。

変数にも型ヒントを書ける

型ヒントは、関数だけでなく変数にも書けます。

name: str = "Python"
age: int = 25
height: float = 175.5
is_student: bool = True

ただし、単純な代入ではPythonやエディタが型を推測できることも多いです。

name = "Python"
age = 25

このような場合、毎回すべての変数に型を書く必要はありません。変数の型ヒントが特に役立つのは、空のリストや辞書を作るときです。

users: list[str] = []
scores: dict[str, int] = {}

空のリストだけを見ると、何を入れる予定なのか分かりません。型ヒントを書いておくと、あとから読んだときに意図が分かりやすくなります。

型ヒントは実行時に強制されない

ここは大事です。Pythonの型ヒントは、基本的には実行時に型を強制しません。例えば、次のコードは型ヒントとしては間違っています。

def double(x: int) -> int:
    return x * 2

print(double("A"))

x: int と書いているので、本来は整数を渡す想定です。しかしPythonは、実行時にこの型ヒントを見て自動で止めるわけではありません。上のコードは、文字列 "A" を2倍して "AA" を出力します。つまり、型ヒントを書いただけで安全になるわけではありません。実際にチェックしたい場合は、mypyやPyrightなどの静的解析ツールを使います。

pip install mypy
mypy example.py

型ヒントは、Python本体というよりも、エディタ、レビュー、静的解析ツールと組み合わせて効く仕組みだと考えると分かりやすいです。

どこまで型を書くべきか

初心者のうちは、全部に型を書こうとしなくて大丈夫です。まずは、次の3つから始めるのが現実的です。

  1. 関数の引数
  2. 関数の戻り値
  3. 空のリストや辞書

特に、関数の入り口と出口に型を書くと、コード全体の見通しがかなりよくなります。逆に、関数の中の一時変数まで細かく書きすぎると、かえって読みにくくなることもあります。型ヒントは「多ければ多いほどよい」というより、読む人が迷いやすい場所に置くのが大事です。

まとめ

Pythonの型ヒントは、コードの意図を明確にするための仕組みです。特に、次のような場面で役立ちます。

  • 関数の引数や戻り値を分かりやすくする
  • None が返る可能性を明示する
  • リストや辞書に何を入れる予定かを示す
  • エディタの補完や警告を効かせやすくする
  • mypyやPyrightで実行前にミスを見つける

型ヒントは、Pythonを静的型付け言語に変えるものではありません。しかし、コードが少し大きくなってきたとき、自分と未来の自分に向けたメモとしてかなり効いてきます。まずは関数の引数と戻り値から、少しずつ書いていくのがおすすめです。

さらに学びたい方へ

LangChainなどのLLMアプリケーションを書くときも、関数の引数や戻り値に型ヒントがあると、コードの見通しがよくなります。

LangChainとOpenAI APIでLLMプログラミングに入門する https://tobarilabo.com/langchain_starter/

独学でも十分進められますが、Pythonの基礎から動画で体系的に学びたい場合は、以下の講座も参考になります。

コメント

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