強制ではなく、意図を伝えるための型アノテーション
Python の型ヒントは、関数の引数などに「ここは int のはず」「戻り値は文字列のリスト」といった情報を書き添えるメモのようなものです。実行時に型チェックが走るわけではないので、int と注釈した場所に文字列を渡してもエラーにはなりません。代わりに、エディタや mypy、pyright(VS Code なら Pylance 経由)といった外部ツールが型ヒントを読み取り、実行前に警告を出してくれる仕組みです。
いちばんシンプルな例を見てみましょう:
name: str は引数の型アノテーション、-> str は戻り値の型アノテーションです。どちらの呼び出しも問題なく動きますが、2つ目は 間違い です。静的型チェッカーなら警告を出しますが、Python自身は 42 がたまたま f"{...}" の文字列補間に対応しているので、何事もなく実行してしまいます。
ここが重要な感覚です。型ヒントは「機械が読めるドキュメント」であって、実行時の挙動は変えません。
python 型ヒントを書くメリット
恩恵は主に3つあります。効果が出るのが早い順に並べてみましょう。
- エディタが賢くなる。 補完が的確なメソッドを出してくれるし、リネームもきちんと伝播します。変数にホバーすれば型もすぐ分かる。
- 関数シグネチャがそれ自体で説明になる。
def fetch(url: str, timeout: float = 5.0) -> dict:と書いてあれば、何を渡して何が返ってくるのかが一目瞭然。関数の中身を読む必要はありません。 - 実行前にバグを見つけられる。 プロジェクトで
mypy .を走らせると、ユニットテストでは拾いきれないようなバグ、たとえば値を期待している場所にNoneが返っている、リストのはずが辞書になっている、といったミスが浮き上がってきます。
今日かぎりの使い捨てスクリプトなら型ヒントは不要です。でも後から見返したり誰かに共有したりする可能性が少しでもあるなら、書くのにかかる15秒は1時間以内に元が取れます。
組み込み型の基本
以下の型はどれもインポート不要でそのまま使えます。
変数アノテーション(name: str = "Rosa")はほとんどの場合、書く必要はありません。右辺の値から Python が型を推論してくれるからです。基本的には、引数・戻り値、それと推論結果が曖昧になるケースだけに絞って使うのがおすすめです。
何も返さない関数には -> None を付けます:
リスト・辞書・タプル・セットの型ヒント
コンテナ型には、もう1つ情報が必要です。何が入っているか、ですね。最近のPythonでは、組み込み型をそのまま型ヒントとして使えます(サブスクリプト記法)。
声に出して読んでみるとこんな感じです。
list[float]— float のリスト。dict[str, int]— キーが文字列、値が int の辞書。tuple[float, float]— ちょうど2つの float からなるタプル。set[str]— 文字列の集合。
list[...] や dict[...] のように [] で要素型を指定する書き方は Python 3.9 以降で使えます。古いコードでは typing から List、Dict、Tuple をインポートしている形をよく見かけますが、意味は同じで、単に古い書き方というだけです。
Optional 型(None を許容する値)
「None になる可能性がある」というケースはよくあります。書き方は2種類あり、どちらでも動きますが、新しい記法のほうが読みやすいです。
str | None は「文字列、または None」という意味です。この | 構文は Python 3.10 以降で使えます。古いコードだと typing モジュールの Optional[str] を見かけると思いますが、意味は同じです。
関数のシグネチャに -> str | None とあれば、呼び出す側は「結果を使う前に None チェックが必要だな」とすぐ分かります。型ヒントをつける意義は、まさにここにあります。
Union 型:「A または B」を表現する
値が複数の型のどれかになり得る場合は、| を使って書きます。
型は2つに限らず、いくつでも並べられます。例えば int | str | float と書けば「この3つのいずれか」という意味になります。
関数内の変数に型ヒントを付ける
関数内のローカル変数については、初期化の値からPythonが型を推論してくれるので、普段は型ヒントを書く必要はありません。アノテーションを付けたほうがいいのは、次のようなケースです:
- コンテナが空の状態でスタートするため、型チェッカーが中身を推論できないとき。
- 値が複数の型を取りうるが、ここでは1つに決めておきたいとき。
- 読み手のために意図をドキュメント化しておきたいとき。
typing.Any は「ここは厳密に型付けしたくない」という逃げ道です。使うのは最小限に留めましょう。Any を乱用すると、せっかく書いた他の型ヒントまで台無しになってしまいます。
クラスに型ヒントを付ける
クラスの属性やメソッドのシグネチャも、通常の関数と同じ要領で型アノテーションを書けます。
データクラスだけは例外で、型アノテーションが必須です。@dataclass デコレーターがアノテーションを読み取って __init__ や __repr__ を自動生成する仕組みになっているからです。型アノテーションが実行時の挙動に影響を与える、唯一と言っていい場面ですね。
タプルと「任意の長さ」パターン
tuple[...] には2通りの書き方があって、初心者がよく混乱するポイントです。
tuple[float, float]— ちょうど2つのfloat。tuple[int, ...]— 任意の数のint。...(型システムで実際に使われる構文)は「以下同様」という意味です。
Callable と型エイリアス
関数が別の関数を受け取ったり返したりする場合は、Callableを使います。
Callable[[int], int] は「int を1つ受け取り、int を返す関数」という意味です。
同じ型注釈を何度も書くのが面倒なときは、別名を付けておきましょう。
エイリアスといっても、実態はただのPythonの代入にすぎません。本来の長い型を書く場所であれば、短い別名でもそのまま使えます。
型チェッカーを実行する
Pythonのインタプリタ自体は型ヒントを無視します。実際に型を チェック するには、型チェッカーを別途インストールする必要があります。定番は mypy、より高速なのがVS CodeのPylanceでも使われている pyright です。
pip install mypy
mypy your_project/
最初に実行すると、思ってもみなかった場所でエラーが次々に出てきます。焦らず少しずつ潰していきましょう。どうしても先に進みたい行があれば、# type: ignore を付けてその行だけチェックを黙らせることができます。
最近のIDEは編集中に型チェックを走らせ続けてくれるので、保存するまでもなくフィードバックが返ってくることがほとんどです。
型ヒントが向かないケース
- 使い捨ての検証スクリプト。 1時間で役目を終えるようなコードに注釈を付けるのは、手間の方が勝ちます。
- 動的な要素が強いコード。 メタプログラミングやプラグイン機構のようなパターンは、型システムで表現しきれないことがよくあります。外向きのAPIだけ注釈を付けて、内部はゆるめに保つのが現実的です。
- 型情報のないサードパーティライブラリ。 取り込んだライブラリに型情報が無いと、
Anyが自分のコードに染み出してきます。これは仕方ありません。自分の責任範囲ではないので割り切りましょう。
それ以外の大半のコードでは、型ヒントは小さな習慣で大きな見返りが得られる投資です。関数シグネチャにキーストロークを少し足すだけで、バグは減り、リファクタは楽になり、コード自身がドキュメントとして機能するようになります。
次は「モジュールとインポート」
これで関数まわりの道具は一通り揃いました——引数、デコレータ、そして型ヒント。次は視点を広げて、Pythonがファイルをまたいでコードをどう整理しているか、つまりモジュール・パッケージ・インポートの仕組みを見ていきます。
よくある質問
Pythonの型ヒント(type hints)とは何ですか?
型ヒントは、変数・関数の引数・戻り値に「こういう型を想定しているよ」と注釈を付ける仕組みです。Python本体は実行時に型をチェックしないので、あくまでIDEやリンター、mypyやpyrightのような型チェッカー、そしてコードを読む人間のための情報、という位置づけになります。
型ヒントを書くとPythonの実行速度は上がりますか?
上がりません。インタプリタは実行時に型ヒントを無視します。速くなるのはあくまで開発サイクルのほうで、エディタがタイポを拾ってくれたり、関数シグネチャが読みやすくなったり、リファクタリングが安全になったり、というメリットが中心です。
型ヒントはいつ書けばいいですか?
まずは公開する関数のシグネチャ(引数と戻り値)に付けるのがおすすめです。関数の中身には、型が一目で分からない変数だけにピンポイントで付ければ十分。使い捨てのスクリプトなら無理に書く必要はありませんが、チームで共有するコードやライブラリなら、書いた分だけすぐに元が取れます。