Python | テスト・設計・品質:typing 型ヒント

Python Python
スポンサーリンク

型ヒントって何?まずは「コメント」だと思ってOK

Python の「型ヒント(type hints)」は、
「この変数や関数は、こういう型を想定しているよ」という情報を、コードの中に書き添える仕組みです。

ポイントは、Python 自体は動的型付けのまま、ということです。
型ヒントを書いても、Python の実行時には基本的にチェックされません。
代わりに、エディタや静的解析ツール(mypy など)がそれを読んで、
「おかしな使い方をしていないか」を教えてくれます。

つまり、型ヒントは「コンピュータと人間の両方のための、超リッチなコメント」です。
コメントと違って、ツールがちゃんと読んでくれる、というのが本質です。


いちばん基本の型ヒント:変数と関数

変数への型ヒント

まずは、変数に型ヒントを付ける一番シンプルな例です。

age: int = 20
name: str = "Taro"
height: float = 172.5
is_active: bool = True
Python

右側の値を見れば型は分かりますが、
型ヒントを書くことで「この変数はこういう意味で使うつもり」という意図がはっきりします。

あとから別の人が読んだとき、
「age は整数なんだな」「name は文字列なんだな」と一瞬で分かります。

関数への型ヒント(ここが一番重要)

型ヒントの真価は、関数に付けたときに発揮されます。

def add(a: int, b: int) -> int:
    return a + b
Python

引数 abint
戻り値も int

ということが、コードから一目で分かります。

もう少し現実的な例を見てみます。

def format_price(price: int, currency: str) -> str:
    return f"{price:,} {currency}"
Python

この関数を見た人は、
「price は整数で、currency は文字列で、結果は文字列なんだな」とすぐ理解できます。

テストを書くときも、呼び出す側のコードを書くときも、
「何を渡して、何が返ってくるのか」が明確になります。


リストや辞書など「コレクション型」の型ヒント

リストの型ヒント

例えば、「整数のリスト」を扱う関数を考えます。

from typing import List

def total(values: List[int]) -> int:
    return sum(values)
Python

List[int] は、「int の要素を持つリスト」という意味です。
List[str] なら「文字列のリスト」です。

Python 3.9 以降なら、もっとシンプルにこう書けます。

def total(values: list[int]) -> int:
    return sum(values)
Python

list[int] のように、組み込みの list にそのまま型パラメータを書けます。

辞書の型ヒント

辞書も同じように書けます。

from typing import Dict

def get_price_map() -> Dict[str, int]:
    return {"apple": 100, "banana": 120}
Python

キーが str、値が int の辞書です。

Python 3.9 以降なら、こう書けます。

def get_price_map() -> dict[str, int]:
    return {"apple": 100, "banana": 120}
Python

これだけで、「この辞書は何をキーにして、何を値にしているのか」が明確になります。
テストを書くときも、「キーは文字列、値は整数」という前提でケースを考えられます。


Optional と Union:「来るかもしれない」「どっちか」の表現

Optional[T] で「None かもしれない」を表す

現実のコードでは、「値がないときは None」というパターンがよく出てきます。

from typing import Optional

def find_user_name(user_id: int) -> Optional[str]:
    if user_id == 1:
        return "Taro"
    return None
Python

Optional[str] は、「str かもしれないし、None かもしれない」という意味です。

Python 3.10 以降なら、もっと短くこう書けます。

def find_user_name(user_id: int) -> str | None:
    ...
Python

この「None かもしれない」を型で表現しておくと、
呼び出す側で「None のときどうする?」をちゃんと考えるきっかけになります。

name = find_user_name(1)
if name is not None:
    print(name.upper())
Python

型ヒントがないと、「うっかり None に対して .upper() を呼んで落ちる」みたいなバグが起きやすくなります。

Union で「複数の型のどれか」を表す

例えば、「整数か文字列のどちらかを受け取る」関数を考えます。

from typing import Union

def to_str(value: Union[int, float, str]) -> str:
    return str(value)
Python

Python 3.10 以降なら、こう書けます。

def to_str(value: int | float | str) -> str:
    return str(value)
Python

「この引数は、これとこれとこれのどれか」という情報が、
型ヒントとしてコードに残ります。


dataclass と型ヒント:設計とテストが一気に楽になる

dataclass で「型付きのデータ構造」を作る

型ヒントは、dataclasses.dataclass と組み合わせると、
「きれいなデータ構造」を簡単に作れます。

from dataclasses import dataclass

@dataclass
class User:
    id: int
    name: str
    email: str
    is_active: bool = True
Python

このクラスを見るだけで、

id は int
name は str
email は str
is_active は bool(デフォルト True)

という情報が一瞬で分かります。

テストを書くときも、
「User をどう初期化するか」が迷いません。

def test_user_default_active():
    user = User(id=1, name="Taro", email="taro@example.com")
    assert user.is_active is True
Python

型ヒントがないと、
「id は文字列でもいいのか?」「is_active は 0/1 でもいいのか?」
といった曖昧さが残ります。

型ヒントを付けることで、
「このクラスはこういう前提で使う」という仕様が、コードとして残ります。


型ヒントとテスト・品質の関係(ここが本当に大事)

型ヒントは「テストの前提条件」を明文化してくれる

テストを書くとき、
「この関数はどんな入力を想定しているか」を理解する必要があります。

型ヒントがあると、それがコードに書いてあります。

def calc_discount(price: int, is_member: bool) -> int:
    ...
Python

この関数に対してテストを書くなら、

price は整数(負の値はどう扱う?)
is_member は bool(True/False の両方をテストしよう)
戻り値も整数(境界値は?)

という観点が自然に出てきます。

型ヒントがないと、
「price に文字列を渡しても動くのか?」
「is_member に 0/1 を渡してもいいのか?」
といった曖昧さが残り、テストの観点もブレやすくなります。

静的解析ツール(mypy など)と組み合わせると「テスト前のテスト」になる

型ヒントを書いておくと、
mypy のような静的型チェッカーが、コードを実行せずにチェックしてくれます。

例えば、こういうコードがあったとします。

def add(a: int, b: int) -> int:
    return a + b

result = add("1", "2")
Python

Python としては実行時までエラーになりませんが、
mypy は「addint を受け取るはずなのに、str を渡している」と教えてくれます。

これは、テストを書く前に「型レベルのバグ」を潰してくれるイメージです。
テストは「ロジックの正しさ」を見るものですが、
型チェックは「使い方のミス」を早期に見つけてくれます。

型ヒント+静的解析+テスト
この三つが揃うと、品質は一気に安定します。


型ヒントでハマりやすいポイントと、初心者向けの付き合い方

最初から全部に付けなくていい

いきなりプロジェクト全体に型ヒントを付けようとすると、
ほぼ確実に挫折します。

現実的には、

新しく書くコードには、最初から型ヒントを付ける
既存コードは、触るところから少しずつ型を付けていく

くらいのペースで十分です。

特に、外から呼ばれる関数(API の入り口、サービス層の関数など)から付けていくと、
効果が出やすいです。

「型が難しいからやめる」ではなく「分かるところだけでも書く」

ジェネリクス、Protocol、TypedDict など、
typing には難しそうな機能がたくさんあります。

でも、最初から全部理解する必要はありません。

int, str, bool, list[int], dict[str, int], Optional[str]
このあたりだけでも、コードの読みやすさはかなり変わります。

「分かるところだけでも型ヒントを書く」
「分からないところは、あとで直せるようにしておく」

このくらいの気持ちで始めるのがちょうどいいです。


まとめ(型ヒントは「未来の自分と他人へのラブレター」)

型ヒントを初心者目線で整理すると、こうなります。

Python の型ヒントは、「この関数・変数はこういう型を想定している」という情報をコードに埋め込む仕組みで、実行時ではなくエディタや静的解析ツールがそれを活用する。
関数やデータ構造に型ヒントを付けることで、「何を渡して何が返るのか」が明確になり、テストの観点も揃いやすくなる。
Optional や Union、list[int] や dict[str, int] など、基本的な型ヒントだけでも、設計の意図と品質がぐっと上がる。
型ヒント+静的解析(mypy など)+テストの組み合わせは、「バグの入り込む余地」を大きく減らし、未来の自分や他の開発者が安心してコードを触れる状態を作ってくれる。

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