typing.Annotatedの活用方法と実践例
typing.Annotated
はPythonの型ヒントに追加のメタデータを付与するための強力なツールです。この機能を理解することで、より表現力豊かな型定義が可能になり、様々なライブラリとの連携がスムーズになります。
問題の核心: Annotatedとは何か
typing.Annotated
は、型情報にメタデータ(追加情報)を付加するための型コンストラクタです。基本的な構文は以下のようになります:
from typing import Annotated
# 基本的な形式
Annotated[型, メタデータ1, メタデータ2, ...]
単体では何も変化を起こしませんが、このメタデータを解釈するライブラリやフレームワークと組み合わせることで、その真価を発揮します。
基本的な使用例
from typing import Annotated
# 文字列型に追加情報を付与
name: Annotated[str, "先頭文字は大文字"]
# 数値型に制約を追加
age: Annotated[int, "0以上120以下"]
実践的な活用例
1. FastAPIでのバリデーション
FastAPIでは、クエリパラメータやリクエストボディのバリデーションにAnnotated
を使用します:
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(
q: Annotated[str, Query(max_length=50, description="検索クエリ")]
):
# qは最大50文字の文字列として検証される
return {"query": q}
2. Pydanticモデルでのバリデーション簡素化
Pydanticでは、Annotated
を使用してバリデーションロジックを簡潔に表現できます:
from typing import Annotated
from pydantic import BaseModel, Field
# 従来の方法(バリデーター関数が必要)
class OldModel(BaseModel):
x: int
@field_validator("x")
def validate_x(cls, v):
if 2 < v < 20:
return v
raise ValueError("2から20の間でなければなりません")
# Annotatedを使用した簡潔な方法
class NewModel(BaseModel):
x: Annotated[int, Field(lt=20, gt=2)]
利点
- コードがより宣言的で簡潔になる
- バリデーションルールが型ヒントに直接記述される
- メンテナンス性が向上する
3. Typer CLIでの引数定義
Typerでは、CLI引数のメタデータをAnnotated
で定義します:
from typing import Annotated
import typer
app = typer.Typer()
@app.command()
def greet(
name: Annotated[str, typer.Argument(help="挨拶する相手の名前")],
formal: Annotated[bool, typer.Option(help="形式張った挨拶にする")] = False
):
if formal:
print(f"こんにちは、{name}さん")
else:
print(f"やあ、{name}!")
4. Strawberry GraphQLでの遅延型解決
GraphQLスキーマで循環依存がある場合、Annotated
を使用して遅延評価できます:
from typing import Annotated
import strawberry
@strawberry.type
class User:
name: str
friends: Annotated[list["User"], strawberry.lazy("app.types")]
メタデータへのアクセス方法
実行時にメタデータにアクセスするには、get_type_hints()
を使用します:
from typing import Annotated, get_type_hints, TypedDict
class State(TypedDict):
value: Annotated[int, "重要度: 高", "単位: メートル"]
# メタデータを含む型ヒントを取得
hints = get_type_hints(State, include_extras=True)
metadata = hints["value"].__metadata__
print(metadata) # ('重要度: 高', '単位: メートル')
カスタムユースケースの作成
Annotated
は自作のライブラリやツールでも活用できます:
from typing import Annotated, get_type_hints, Type
def validate_with_metadata(obj: object, data: dict):
hints = get_type_hints(type(obj), include_extras=True)
for field, hint in hints.items():
if hasattr(hint, '__metadata__'):
metadata = hint.__metadata__
# メタデータに基づいてバリデーションを実行
for validator in metadata:
if callable(validator):
data[field] = validator(data[field])
return data
# 使用例
class Config:
timeout: Annotated[int, lambda x: min(max(x, 0), 300)] # 0-300の範囲に制限
config_data = {"timeout": 500}
validated = validate_with_metadata(Config, config_data)
print(validated) # {"timeout": 300}
ベストプラクティス
- メタデータは軽量に保つ: 大量のデータや複雑なオブジェクトは避ける
- 互換性を考慮: メタデータを無視するツールもあることを念頭に置く
- ドキュメンテーション: カスタムメタデータの目的と使用方法を文書化する
- タイプチェッカーの互換性: 主要なタイプチェッカー(mypy, pyrightなど)での動作を確認する
注意点
Annotated
のメタデータは型チェッカーによって無視されることがあります。実行時の挙動に依存する場合は、適切なライブラリやツールを使用してください。
まとめ
typing.Annotated
は、型システムに柔軟性と表現力を追加する強力な機能です。主要なPythonフレームワーク(FastAPI、Pydantic、Typer、Strawberryなど)で広く採用されており、現代的なPythonコードでは必須の知識となっています。メタデータを通じて型の意味や制約を表現することで、より堅牢でメンテナブルなコードを書くことができます。