Python型ヒント: 型注釈の実践ガイド
Updated on
Pythonの動的型付けは柔軟性に優れていますが、大規模になれば実際の問題を引き起こします。関数は誤った型を黙って受け入れ、実際のバグから遠い場所で混乱したエラーを発生させます。リファクタリングは予測不可能な箇所でコードを破壊します。他の人のコードを読む際には、各変数、各関数パラメータ、各戻り値を通過する型を推測する必要があります。コードベースが成長するにつれ、これらの推測はバグに変わり、そのバグはデバッグ時間に変わっていきます。
チーム環境ではこのコストがさらに増大します。型情報がないと、関数呼び出しごとに実装を読んで何を期待し何を返すかを理解する必要があります。コードレビューは遅くなります。新しいチームメンバーのオンボーディングには時間がかかります。自動化ツールは動作するための型情報がないため助けになることができません。
Pythonの型ヒントは、IDE、型チェッカー、人間が検証できるオプションの型注釈を追加することでこれを解決します。意図をコードに直接文書化し、実行前にバグのカテゴリー全体を捕捉し、オートコンプリートやインラインエラー検出などの強力なエディタ機能を解放します。このガイドでは、基本的な注釈から本番のPythonコードベースで使用される高度なパターンまで、すべてをカバーします。
型ヒントとは何か?
型ヒントは、変数、関数パラメータ、戻り値の期待される型を指定するオプションの注釈です。PEP 484 (opens in a new tab)(Python 3.5)で導入され、PEP 526(変数注釈)、PEP 604(ユニオン構文)、PEP 612(パラメータ仕様)などの後続のPEPによって改良されてきました。
重要なキーワードはオプションです。型ヒントは実行時の動作には影響しません。Pythonは実行中にそれらを強制しません。それらは3つの対象者のために存在します:コードを読む開発者、オートコンプリートやエラー検出を提供するIDE、そしてコードを実行せずに分析するmypyのような静的型チェッカーです。
# 型ヒントなし - この関数は何を期待しているか?
def process_data(data, threshold, output):
...
# 型ヒントあり - 一目瞭然
def process_data(data: list[float], threshold: float, output: str) -> dict[str, float]:
...2番目のバージョンは一目ですべてを伝えます:dataはfloatのリスト、thresholdはfloat、outputは文字列で、関数は文字列からfloatへの辞書を返します。実装を読んだり呼び出し元を辿ったりする必要はありません。
基本的な型注釈
変数注釈
変数注釈は、PEP 526(Python 3.6)で導入されたコロン構文を使用します:
# 基本的な変数注釈
name: str = "Alice"
age: int = 30
height: float = 5.9
is_active: bool = True
raw_data: bytes = b"hello"
# 代入なしの注釈(宣言のみ)
username: str
count: int値を代入せずに変数に注釈を付けることができます。これは、後で変数が代入されるクラス本体や条件ブロックで役立ちます。
関数パラメータと戻り値の型
関数注釈は、パラメータにはコロンを、戻り値には->を使用します:
def greet(name: str) -> str:
return f"Hello, {name}!"
def calculate_average(numbers: list[float]) -> float:
return sum(numbers) / len(numbers)
def save_record(record: dict[str, str], overwrite: bool = False) -> None:
"""意味のある値を返さずアクションを実行する関数は -> None を使用します。"""
...-> None注釈は、関数が意味のある値を返さずにアクションを実行することを明示的に伝えます。これは、意図的なNoneの戻りと、忘れられたreturn文を区別するため重要です。
組み込み型
Pythonの組み込み型は型ヒントに直接マッピングされます:
| 型 | 例 | 説明 |
|---|---|---|
int | count: int = 10 | 整数 |
float | price: float = 9.99 | 浮動小数点数 |
str | name: str = "Bob" | 文字列 |
bool | active: bool = True | ブール値 |
bytes | data: bytes = b"\x00" | バイト列 |
None | result: None = None | Noneシングルトン |
def parse_config(path: str, encoding: str = "utf-8") -> dict[str, str]:
config: dict[str, str] = {}
with open(path, encoding=encoding) as f:
for line in f:
key, _, value = line.partition("=")
config[key.strip()] = value.strip()
return configコレクション型
モダン構文(Python 3.9+)
Python 3.9以降では、組み込みのコレクション型をジェネリック型として直接使用できます:
# リスト
scores: list[int] = [95, 87, 92]
names: list[str] = ["Alice", "Bob"]
# 辞書
user_ages: dict[str, int] = {"Alice": 30, "Bob": 25}
config: dict[str, list[str]] = {"servers": ["a.com", "b.com"]}
# タプル - 固定長で特定の型
point: tuple[float, float] = (3.14, 2.72)
record: tuple[str, int, bool] = ("Alice", 30, True)
# 可変長タプル(すべて同じ型)
values: tuple[int, ...] = (1, 2, 3, 4, 5)
# セットとfrozenset
tags: set[str] = {"python", "typing"}
constants: frozenset[int] = frozenset({1, 2, 3})レガシー構文(Python 3.5-3.8)
Python 3.9より前は、typingモジュールからジェネリック型をインポートする必要がありました:
from typing import List, Dict, Tuple, Set, FrozenSet
scores: List[int] = [95, 87, 92]
user_ages: Dict[str, int] = {"Alice": 30}
point: Tuple[float, float] = (3.14, 2.72)
tags: Set[str] = {"python", "typing"}構文比較表
| 型 | Python 3.9+ | Python 3.5-3.8 |
|---|---|---|
| リスト | list[int] | typing.List[int] |
| 辞書 | dict[str, int] | typing.Dict[str, int] |
| タプル(固定) | tuple[str, int] | typing.Tuple[str, int] |
| タプル(可変) | tuple[int, ...] | typing.Tuple[int, ...] |
| セット | set[str] | typing.Set[str] |
| FrozenSet | frozenset[int] | typing.FrozenSet[int] |
| 型 | type[MyClass] | typing.Type[MyClass] |
プロジェクトがPython 3.9以降を対象とする場合は、常にモダン構文を使用してください。よりクリーンで、インポートも不要です。
ネストしたコレクション
コレクション型は、複雑なデータ構造のために自然に合成されます:
# マトリックス: floatのリストのリスト
matrix: list[list[float]] = [[1.0, 2.0], [3.0, 4.0]]
# ユーザーとそのスコアリストのマッピング
gradebook: dict[str, list[int]] = {
"Alice": [95, 87, 92],
"Bob": [78, 85, 90],
}
# 設定: ネストした辞書
app_config: dict[str, dict[str, str | int]] = {
"database": {"host": "localhost", "port": 5432},
"cache": {"host": "redis.local", "port": 6379},
}OptionalとUnion型
Optional型
Noneになりうる値は、Optionalまたはユニオン構文で注釈されます:
from typing import Optional
# 3.10以前の構文
def find_user(user_id: int) -> Optional[str]:
"""ユーザー名を返すか、見つからない場合はNoneを返します。"""
users = {1: "Alice", 2: "Bob"}
return users.get(user_id)
# Python 3.10+ 構文(推奨)
def find_user(user_id: int) -> str | None:
"""ユーザー名を返すか、見つからない場合はNoneを返します。"""
users = {1: "Alice", 2: "Bob"}
return users.get(user_id)Optional[str]はstr | Noneと完全に等価です。パイプ構文はより読みやすく、インポートも不要です。
Union型
値がいくつかの型のいずれかである場合:
from typing import Union
# 3.10以前の構文
def format_value(value: Union[int, float, str]) -> str:
return str(value)
# Python 3.10+ 構文(推奨)
def format_value(value: int | float | str) -> str:
return str(value)
# 一般的なパターン: 複数の入力形式を受け入れる
def load_data(source: str | Path) -> list[dict[str, str]]:
path = Path(source) if isinstance(source, str) else source
with open(path) as f:
return json.load(f)Union構文の比較
| パターン | 3.10以前 | Python 3.10+ |
|---|---|---|
| Nullable | Optional[str] | str | None |
| 2つの型 | Union[int, str] | int | str |
| 複数 | Union[int, float, str] | int | float | str |
| Nullable union | Optional[Union[int, str]] | int | str | None |
typingモジュールの高度な型
Any
Anyは、特定の値の型チェックを無効にします。エスケープハッチとして控えめに使用してください:
from typing import Any
def log_event(event: str, payload: Any) -> None:
"""あらゆるペイロード型を受け入れます - 汎用ロギングに有用です。"""
print(f"[{event}] {payload}")
# Anyはすべての型と互換性があります
log_event("click", {"x": 100, "y": 200})
log_event("error", 404)
log_event("message", "hello")TypeVarとGeneric
TypeVarは、型関係を保持しながら複数の型で動作する関数やクラスのためのジェネリック型変数を作成します:
from typing import TypeVar
T = TypeVar("T")
def first_element(items: list[T]) -> T:
"""最初の要素を返し、その型を保持します。"""
return items[0]
# 型チェッカーは正しい戻り値の型を推論します
name = first_element(["Alice", "Bob"]) # type: str
score = first_element([95, 87, 92]) # type: int
# 境界付きTypeVar - 特定の型に制限
Numeric = TypeVar("Numeric", int, float)
def add(a: Numeric, b: Numeric) -> Numeric:
return a + bジェネリッククラスの作成:
from typing import TypeVar, Generic
T = TypeVar("T")
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
def peek(self) -> T:
return self._items[-1]
def is_empty(self) -> bool:
return len(self._items) == 0
# 特定の型での使用
int_stack: Stack[int] = Stack()
int_stack.push(42)
value: int = int_stack.pop()
str_stack: Stack[str] = Stack()
str_stack.push("hello")Callable
Callableは関数パラメータとコールバックに注釈を付けます:
from typing import Callable
# コールバックを取る関数
def apply_operation(
values: list[float],
operation: Callable[[float], float]
) -> list[float]:
return [operation(v) for v in values]
# 使用例
import math
results = apply_operation([1.0, 4.0, 9.0], math.sqrt)
# results: [1.0, 2.0, 3.0]
# より複雑なcallableシグネチャ
Comparator = Callable[[str, str], int]
EventHandler = Callable[[str, dict[str, str]], None]
def sort_with_comparator(
items: list[str],
compare: Comparator
) -> list[str]:
import functools
return sorted(items, key=functools.cmp_to_key(compare))Literal
Literalは値を特定の定数に制限します:
from typing import Literal
def set_log_level(level: Literal["DEBUG", "INFO", "WARNING", "ERROR"]) -> None:
print(f"Log level set to {level}")
set_log_level("INFO") # OK
set_log_level("VERBOSE") # 型エラー: 有効なリテラルではありません
# モードパラメータに有用
def read_file(
path: str,
mode: Literal["text", "binary"] = "text"
) -> str | bytes:
if mode == "text":
return open(path).read()
return open(path, "rb").read()TypedDict
TypedDictは、特定のキーと値の型を持つ辞書の形状を定義します:
from typing import TypedDict, NotRequired
class UserProfile(TypedDict):
name: str
email: str
age: int
bio: NotRequired[str] # オプションのキー (Python 3.11+)
def display_user(user: UserProfile) -> str:
return f"{user['name']} ({user['email']}), age {user['age']}"
# 型チェッカーは構造を検証します
user: UserProfile = {
"name": "Alice",
"email": "alice@example.com",
"age": 30,
}
display_user(user) # OKProtocol(構造的型付け)
Protocolは、継承ではなく構造に基づいてインターフェースを定義します。オブジェクトが必要なメソッドを持っていれば、そのプロトコルを満たします:
from typing import Protocol, runtime_checkable
@runtime_checkable
class Drawable(Protocol):
def draw(self, x: int, y: int) -> None: ...
class Circle:
def draw(self, x: int, y: int) -> None:
print(f"Drawing circle at ({x}, {y})")
class Square:
def draw(self, x: int, y: int) -> None:
print(f"Drawing square at ({x}, {y})")
def render(shape: Drawable, x: int, y: int) -> None:
shape.draw(x, y)
# Drawableから継承しなくても両方とも動作します
render(Circle(), 10, 20)
render(Square(), 30, 40)
# runtime_checkableによりisinstanceチェックが可能になります
print(isinstance(Circle(), Drawable)) # TrueTypeAlias
TypeAliasは、複雑な型の明示的な型エイリアスを作成します:
from typing import TypeAlias
# 単純なエイリアス
UserId: TypeAlias = int
JsonDict: TypeAlias = dict[str, "JsonValue"]
JsonValue: TypeAlias = str | int | float | bool | None | list["JsonValue"] | JsonDict
# 複雑なエイリアスはシグネチャを簡潔にします
Matrix: TypeAlias = list[list[float]]
Callback: TypeAlias = Callable[[str, int], bool]
Config: TypeAlias = dict[str, str | int | list[str]]
def transform_matrix(m: Matrix, factor: float) -> Matrix:
return [[cell * factor for cell in row] for row in m]
# Python 3.12+ ではtype文を使用します
# type Vector = list[float]mypyによる型チェック
mypyのインストールと実行
mypyはPythonで最も広く使用されている静的型チェッカーです:
# mypyのインストール
# pip install mypy
# 単一ファイルのチェック
# mypy script.py
# プロジェクト全体のチェック
# mypy src/
# 特定のPythonバージョンでチェック
# mypy --python-version 3.10 src/設定
pyproject.tomlまたはmypy.iniでプロジェクト全体の設定を構成します:
# pyproject.tomlの設定
# [tool.mypy]
# python_version = "3.10"
# warn_return_any = true
# warn_unused_configs = true
# disallow_untyped_defs = true
# check_untyped_defs = true
# no_implicit_optional = true
# strict_equality = true
# モジュールごとのオーバーライド
# [[tool.mypy.overrides]]
# module = "third_party_lib.*"
# ignore_missing_imports = true一般的なmypyフラグ
| フラグ | 効果 |
|---|---|
--strict | すべての厳格なチェックを有効化(新規プロジェクトで推奨) |
--ignore-missing-imports | 型付けされていないサードパーティライブラリのエラーをスキップ |
--disallow-untyped-defs | すべての関数に型注釈を必須にする |
--no-implicit-optional | NoneのデフォルトをOptionalとして扱わない |
--warn-return-any | 型付けされた関数からAnyを返す場合に警告 |
--show-error-codes | 各エラーのエラーコードを表示(抑制に有用) |
一般的なmypyエラーの修正
# エラー: 互換性のない戻り値の型 (got "Optional[str]", expected "str")
# 修正: Noneの場合を処理する
def get_name(user_id: int) -> str:
result = lookup(user_id) # str | None を返す
if result is None:
raise ValueError(f"User {user_id} not found")
return result # mypyはresultがstrであることを認識します
# エラー: "Optional[str]"の項目"None"に属性"upper"がありません
# 修正: まず型を絞り込む
def format_name(name: str | None) -> str:
if name is not None:
return name.upper()
return "UNKNOWN"
# エラー: 変数の型注釈が必要です
# 修正: 明示的な注釈を追加
items: list[str] = [] # 単なる items = [] ではなく
# エラー: 代入における互換性のない型
# 修正: Unionを使用するか型を修正
value: int | str = 42
value = "hello" # union型ではOK
# 必要に応じて特定のエラーを抑制
x = some_untyped_function() # type: ignore[no-untyped-call]実践での型ヒント
FastAPIとPydantic
FastAPIは型ヒントを使用して、リクエストの検証、シリアライゼーション、およびドキュメントを実現します:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UserCreate(BaseModel):
name: str
email: str
age: int
tags: list[str] = []
class UserResponse(BaseModel):
id: int
name: str
email: str
@app.post("/users", response_model=UserResponse)
async def create_user(user: UserCreate) -> UserResponse:
# FastAPIはリクエストボディをUserCreateに対して検証し
# レスポンスをUserResponseに合わせてシリアライズします
return UserResponse(id=1, name=user.name, email=user.email)Pydanticは型ヒントを使用して、データの自動検証、型変換、およびJSONスキーマの生成を行います。型注釈はドキュメントだけでなく、実行時の動作を駆動します。
データサイエンス: DataFrameの型付け
型ヒントはデータサイエンスワークフローでますます重要になっています:
import pandas as pd
from typing import Any
# 基本的なDataFrameの型付け
def clean_dataframe(df: pd.DataFrame) -> pd.DataFrame:
return df.dropna().reset_index(drop=True)
# panderaによるスキーマ検証の使用
# pip install pandera
import pandera as pa
from pandera.typing import DataFrame, Series
class SalesSchema(pa.DataFrameModel):
product: Series[str]
quantity: Series[int] = pa.Field(ge=0)
price: Series[float] = pa.Field(gt=0)
date: Series[pd.Timestamp]
@pa.check_types
def process_sales(df: DataFrame[SalesSchema]) -> DataFrame[SalesSchema]:
"""型チェックされたDataFrame処理。"""
return df[df["quantity"] > 0]IDEのメリット
型ヒントは、すべての主要なエディタで強力なIDE機能のロックを解除します:
| 機能 | 型ヒントなし | 型ヒントあり |
|---|---|---|
| オートコンプリート | 一般的な候補 | 正確な型のコンテキストに応じた候補 |
| エラー検出 | 実行時エラーのみ | 実行前のインラインエラー |
| リファクタリング | 手動の検索と置換 | 安全な自動名前変更とリファクタリング |
| ドキュメント | docstringやソース読み込みが必要 | ホバーで型をインライン表示 |
| ナビゲーション | テキスト検索 | 型付けされた定義と実装へのジャンプ |
ツール横断的な型ヒントのメリット
| ツール | 型ヒントの使用方法 |
|---|---|
| mypy | 静的型チェック、実行前のバグ捕捉 |
| Pyright/Pylance | VS Codeでの型チェックとオートコンプリート |
| FastAPI | リクエスト/レスポンスの検証とAPIドキュメント |
| Pydantic | データ検証、シリアライゼーション、設定管理 |
| SQLAlchemy 2.0 | マッピングされたカラム、クエリ結果の型 |
| pytest | プラグインの型推論、フィクスチャの型付け |
| attrs/dataclasses | 自動__init__、__repr__、__eq__生成 |
型ヒントチートシート
ここに最も一般的な型注釈のクイックリファレンス表があります:
| 注釈 | 意味 | 例 |
|---|---|---|
int | 整数 | count: int = 0 |
float | 浮動小数点数 | price: float = 9.99 |
str | 文字列 | name: str = "Alice" |
bool | ブール値 | active: bool = True |
bytes | バイト | data: bytes = b"" |
None | None型 | -> None |
list[int] | intのリスト | scores: list[int] = [] |
dict[str, int] | strからintへの辞書 | ages: dict[str, int] = {} |
tuple[str, int] | 固定長タプル | pair: tuple[str, int] |
tuple[int, ...] | 可変長タプル | nums: tuple[int, ...] |
set[str] | 文字列のセット | tags: set[str] = set() |
str | None | Nullable文字列 (3.10+) | name: str | None = None |
Optional[str] | Nullable文字列 (3.10以前) | name: Optional[str] = None |
int | str | intまたは文字列 (3.10+) | value: int | str |
Union[int, str] | intまたは文字列 (3.10以前) | value: Union[int, str] |
Any | 任意の型(エスケープハッチ) | data: Any |
Callable[[int], str] | 関数型 | fn: Callable[[int], str] |
Literal["a", "b"] | 特定の値のみ | mode: Literal["r", "w"] |
TypeVar("T") | ジェネリック型変数 | T = TypeVar("T") |
ClassVar[int] | クラスレベル変数 | count: ClassVar[int] = 0 |
Final[str] | 再代入不可 | NAME: Final = "app" |
TypeAlias | 明示的な型エイリアス | UserId: TypeAlias = int |
よくある間違い
| 間違い | 問題 | 修正 |
|---|---|---|
def f(x: list) | 要素型が欠落 | def f(x: list[int]) |
items = [] | 型を推論できない | items: list[str] = [] |
def f(x: int = None) | デフォルトはNoneだが型はint | def f(x: int | None = None) |
from typing import List (3.9+) | 不要なインポート | list[int]を直接使用 |
def f(x: dict) | キー/値の型が欠落 | def f(x: dict[str, int]) |
isinstance(x, list[int]) | ジェネリクスをisinstanceで使用不可 | isinstance(x, list) |
def f() -> True | 値を使用しているが型ではない | def f() -> bool |
selfに注釈 | 冗長、mypyは推論する | selfの注釈を省略 |
x: str = 42 | 間違った注釈 | 注釈を実際の型に合わせる |
Anyの過度の使用 | 型付けの目的を損なう | 具体的な型またはTypeVarを使用 |
# 間違い: 変更可能なデフォルト値に型ヒント
def bad_append(item: str, items: list[str] = []) -> list[str]:
items.append(item) # 共有される変更可能なデフォルト!
return items
# 修正: Noneをデフォルトとして使用
def good_append(item: str, items: list[str] | None = None) -> list[str]:
if items is None:
items = []
items.append(item)
return items実践例: 型付きデータパイプライン
ここに、型ヒントが実際のデータ処理シナリオでどのように連携するかを示す完全な例があります。このパターンはデータサイエンスや分析ワークフローで一般的です:
from typing import TypedDict, Callable, TypeAlias
from pathlib import Path
import csv
# TypedDictでデータ形状を定義
class RawRecord(TypedDict):
name: str
value: str
category: str
class ProcessedRecord(TypedDict):
name: str
value: float
category: str
normalized: float
# 変換関数の型エイリアス
Transform: TypeAlias = Callable[[list[ProcessedRecord]], list[ProcessedRecord]]
def load_csv(path: Path) -> list[RawRecord]:
"""型付き出力でCSVデータを読み込みます。"""
records: list[RawRecord] = []
with open(path) as f:
reader = csv.DictReader(f)
for row in reader:
records.append(RawRecord(
name=row["name"],
value=row["value"],
category=row["category"],
))
return records
def parse_records(raw: list[RawRecord]) -> list[ProcessedRecord]:
"""生の文字列レコードを型付きレコードに変換します。"""
max_value = max(float(r["value"]) for r in raw) or 1.0
return [
ProcessedRecord(
name=r["name"],
value=float(r["value"]),
category=r["category"],
normalized=float(r["value"]) / max_value,
)
for r in raw
]
def filter_by_category(
records: list[ProcessedRecord],
category: str
) -> list[ProcessedRecord]:
"""レコードをフィルタリングし、入力と出力を完全に型付けします。"""
return [r for r in records if r["category"] == category]
def apply_transforms(
records: list[ProcessedRecord],
transforms: list[Transform]
) -> list[ProcessedRecord]:
"""型付き変換関数のチェーンを適用します。"""
result = records
for transform in transforms:
result = transform(result)
return result
# 使用例
raw = load_csv(Path("data.csv"))
processed = parse_records(raw)
filtered = filter_by_category(processed, "electronics")このパイプラインのすべての関数には、明確な入力型と出力型があります。型チェッカーは、ある関数の出力が次の関数の入力と一致するか検証できます。ProcessedRecord構造を変更した場合、mypyは更新が必要なすべての場所をフラグします。
PyGWalkerによる型付きデータの可視化
データサイエンスワークフローで型付きDataFrameを扱う際、PyGWalker (opens in a new tab)はpandasのDataFrameをインタラクティブなTableauライクな可視化インターフェースに変換します。適切な型付けによって生成された構造化データと連携して、探索可能なチャートやダッシュボードに直接フィードされます:
import pandas as pd
import pygwalker as pyg
# 型付きパイプラインがクリーンで構造化されたデータを生成
data: list[ProcessedRecord] = parse_records(raw)
df = pd.DataFrame(data)
# PyGWalkerがインタラクティブな可視化としてレンダリング
walker = pyg.walk(df)インタラクティブなノートブック環境では、RunCell (opens in a new tab)は、型チェックされたコードと視覚的なデータ探索がシームレスに連携するAI駆動のJupyter体験を提供します。
FAQ
型ヒントはPythonのパフォーマンスに影響しますか?
いいえ。Pythonのランタイムは型ヒントを完全に無視します。関数や変数にメタデータとして保存されますが、通常の実行中は評価されません。注釈を保存するためのわずかなメモリオーバーヘッドがありますが、実行速度への影響はありません。PydanticやFastAPIのようなフレームワークは、検証ロジックを構築するために起動時に注釈を読み取りますが、これはフレームワークの動作であり、Python言語機能ではありません。
Pythonで型ヒントは必須ですか?
いいえ。型ヒントは完全にオプションです。Pythonは動的型付け言語であり、注釈の有無にかかわらず同じように実行されます。ただし、型ヒントは複数の開発者がいるプロジェクトや長期的なメンテナンスが必要なコードベースに強く推奨されています。FastAPI、SQLAlchemy、Djangoなどの主要なPythonプロジェクトは、ますます型ヒントに依存しています。
型ヒントと型チェックの違いは何ですか?
型ヒントはコードに書く注釈(x: intや-> strなど)です。型チェックは、コードがこれらの注釈と一致しているか検証するプロセスです。型チェックは、mypy、Pyright、Pylanceのような外部ツールによって実行され、Python自体によって行われるわけではありません。型チェッカーを実行せずに型ヒントを持つことは可能ですし、それでもIDEのオートコンプリートやドキュメントを通じて価値を提供します。
型ヒントにはtyping.Listとlistのどちらを使用すべきですか?
プロジェクトがPython 3.9以降を対象とする場合は、小文字のlist[int]を使用してください。大文字のtyping.List[int]はPython 3.5-3.8で必要なレガシー構文です。小文字構文はよりクリーンで、インポートも不要であり、今後推奨されるアプローチです。dictとtyping.Dict、tupleとtyping.Tuple、setとtyping.Setも同様です。
Pythonで最適な型チェッカーは何ですか?
mypyは、最も確立されて広く使用されているPythonの型チェッカーです。Pyright(VS CodeのPylance拡張機能で使用)は高速で、mypyが見逃す一部のエラーを捕捉します。両方とも積極的にメンテナンスされています。ほとんどのプロジェクトでは、エディタとの統合が最も優れたものを使用してください。mypyはCIパイプラインの標準です。PyrightはVS Codeで最優秀のリアルタイム体験を提供します。両方を競合なく同じプロジェクトで実行できます。
まとめ
Pythonの型ヒントは、Pythonの動的柔軟性と静的型付け言語の安全性の保証との間のギャップを埋めます。実行前にバグを捕捉し、コードを自己文書化し、開発を高速化する強力なIDE機能のロックを解除します。
基本から始めましょう:関数パラメータ、戻り値の型、複雑な変数に注釈を付けます。日常の型にはlist[int]、dict[str, str]、str | Noneを使用します。CIパイプラインでmypyを実行して、型エラーを自動的に捕捉します。自信がついたら、TypedDict、Protocol、Genericのような高度なパターンを採用して、複雑なドメイン型をモデル化します。
投資はすぐに報われます。本番バグを防ぐ単一の型注釈が、何時間もの入力作業を正当化します。チームは、型ヒントの採用後、より迅速なオンボーディング、より安全なリファクタリング、より少ない実行時の予期せぬ事態を報告しています。FastAPIやPydanticのようなフレームワークが型注釈を中心に設計を構築していることから、型付きPythonはニッチな実践ではなく、エコシステムが進む方向なのです。