Skip to content

typing.Annotatedの活用方法と実践例

typing.AnnotatedはPythonの型ヒントに追加のメタデータを付与するための強力なツールです。この機能を理解することで、より表現力豊かな型定義が可能になり、様々なライブラリとの連携がスムーズになります。

問題の核心: Annotatedとは何か

typing.Annotatedは、型情報にメタデータ(追加情報)を付加するための型コンストラクタです。基本的な構文は以下のようになります:

python
from typing import Annotated

# 基本的な形式
Annotated[型, メタデータ1, メタデータ2, ...]

単体では何も変化を起こしませんが、このメタデータを解釈するライブラリやフレームワークと組み合わせることで、その真価を発揮します。

基本的な使用例

python
from typing import Annotated

# 文字列型に追加情報を付与
name: Annotated[str, "先頭文字は大文字"]

# 数値型に制約を追加
age: Annotated[int, "0以上120以下"]

実践的な活用例

1. FastAPIでのバリデーション

FastAPIでは、クエリパラメータやリクエストボディのバリデーションにAnnotatedを使用します:

python
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を使用してバリデーションロジックを簡潔に表現できます:

python
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で定義します:

python
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を使用して遅延評価できます:

python
from typing import Annotated
import strawberry

@strawberry.type
class User:
    name: str
    friends: Annotated[list["User"], strawberry.lazy("app.types")]

メタデータへのアクセス方法

実行時にメタデータにアクセスするには、get_type_hints()を使用します:

python
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は自作のライブラリやツールでも活用できます:

python
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}

ベストプラクティス

  1. メタデータは軽量に保つ: 大量のデータや複雑なオブジェクトは避ける
  2. 互換性を考慮: メタデータを無視するツールもあることを念頭に置く
  3. ドキュメンテーション: カスタムメタデータの目的と使用方法を文書化する
  4. タイプチェッカーの互換性: 主要なタイプチェッカー(mypy, pyrightなど)での動作を確認する

注意点

Annotatedのメタデータは型チェッカーによって無視されることがあります。実行時の挙動に依存する場合は、適切なライブラリやツールを使用してください。

まとめ

typing.Annotatedは、型システムに柔軟性と表現力を追加する強力な機能です。主要なPythonフレームワーク(FastAPI、Pydantic、Typer、Strawberryなど)で広く採用されており、現代的なPythonコードでは必須の知識となっています。メタデータを通じて型の意味や制約を表現することで、より堅牢でメンテナブルなコードを書くことができます。