第2章:モダン構文

更新日:2025年12月9日

本章では、Python 3.5以降で導入されたモダンな構文と機能を解説する。型ヒントによる静的解析、dataclassとPydanticによるデータ構造定義、構造的パターンマッチング(match文)、async/awaitによる非同期処理、Python 3.10から3.13までの新機能について学ぶ。これらの機能は、大規模プロジェクトの保守性と開発効率を大幅に向上させる。

1. 型ヒントと静的解析

1.1 typing基礎

型ヒント(Type Hints)はPEP 484で導入され、Python 3.5以降で利用可能である[1]。型ヒントは実行時には無視されるが、静的解析ツール(mypy、pyright等)によるエラー検出を可能にする。

1.1.1 基本的な型アノテーション:変数、関数引数、戻り値に型を指定できる。

# 変数への型ヒント
name: str = "Python"
age: int = 30
scores: list[int] = [85, 90, 78]
config: dict[str, str] = {"host": "localhost", "port": "8080"}

# 関数への型ヒント
def greet(name: str, times: int = 1) -> str:
    return f"Hello, {name}! " * times

# Python 3.10以降: X | Yでユニオン型
def process(value: int | str) -> str:
    return str(value)

1.1.2 typingモジュールの主要な型:Table 1に頻出する型を示す。

Table 1. typingモジュールの主要な型

用途 Python 3.9+での代替
List[T] リスト list[T]
Dict[K, V] 辞書 dict[K, V]
Optional[T] T | None T | None
Union[A, B] AまたはB A | B
Callable[[Args], R] 呼び出し可能オブジェクト collections.abc.Callable
Any 任意の型(型チェック無効化) -
TypeVar ジェネリック型変数 -
from typing import Callable, TypeVar

T = TypeVar('T')

# ジェネリック関数
def first(items: list[T]) -> T | None:
    return items[0] if items else None

# 高階関数の型付け
def apply_twice(f: Callable[[int], int], x: int) -> int:
    return f(f(x))

result = apply_twice(lambda n: n * 2, 5)  # 20

1.2 Protocol/Generic

1.2.1 Protocol:構造的部分型(Structural Subtyping)を実現する。クラスが特定のメソッドやプロパティを持っていれば、明示的な継承なしにプロトコルを満たすとみなされる[2]。

from typing import Protocol

class Drawable(Protocol):
    def draw(self) -> None: ...

class Circle:
    def draw(self) -> None:
        print("Drawing circle")

class Square:
    def draw(self) -> None:
        print("Drawing square")

def render(shape: Drawable) -> None:
    shape.draw()

# Circle, SquareはDrawableを継承していないが、
# draw()メソッドを持つため、Drawableプロトコルを満たす
render(Circle())  # OK
render(Square())  # OK

1.2.2 Generic:型パラメータを持つクラスを定義できる。Python 3.12以降では簡潔な構文が利用可能。

# Python 3.11以前
from typing import Generic, TypeVar

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()

# Python 3.12以降: 簡潔な構文
class Stack[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()

int_stack: Stack[int] = Stack()
int_stack.push(42)

2. dataclassとPydantic

2.1 dataclass:Python 3.7で導入された標準ライブラリの機能で、データを保持するクラスを簡潔に定義できる[3]。

from dataclasses import dataclass, field
from datetime import datetime

@dataclass
class User:
    name: str
    email: str
    age: int = 0
    created_at: datetime = field(default_factory=datetime.now)

user = User("Alice", "alice@example.com", 30)
print(user)  # User(name='Alice', email='alice@example.com', age=30, created_at=...)

# イミュータブルなdataclass
@dataclass(frozen=True)
class Point:
    x: float
    y: float

p = Point(1.0, 2.0)
# p.x = 3.0  # FrozenInstanceError

2.2 dataclassの主要オプション:Table 2にデコレータのオプションを示す。

Table 2. @dataclassの主要オプション

オプション デフォルト 効果
frozen False Trueでイミュータブル化
order False Trueで比較メソッド生成(<, <=, >, >=)
slots False True で __slots__ 使用(3.10+)
kw_only False Trueで全フィールドをキーワード専用に(3.10+)

2.3 Pydantic:バリデーション機能を備えたデータクラスライブラリ。FastAPIのリクエスト/レスポンスモデルとして広く使用される。

from pydantic import BaseModel, EmailStr, field_validator

class UserCreate(BaseModel):
    name: str
    email: EmailStr
    age: int

    @field_validator('age')
    @classmethod
    def age_must_be_positive(cls, v: int) -> int:
        if v < 0:
            raise ValueError('age must be positive')
        return v

# バリデーション成功
user = UserCreate(name="Alice", email="alice@example.com", age=30)

# バリデーション失敗
try:
    invalid = UserCreate(name="Bob", email="invalid-email", age=-5)
except Exception as e:
    print(e)  # validation error details

2.4 dataclass vs Pydantic:Table 3に両者の比較を示す。

Table 3. dataclassとPydanticの比較

観点 dataclass Pydantic
依存関係 標準ライブラリ 外部パッケージ
バリデーション なし 強力な組み込み機能
JSON変換 手動実装が必要 model_dump(), model_validate()
パフォーマンス 高速 バリデーションコストあり
用途 内部データ構造 API境界、設定管理

3. 構造的パターンマッチング

Python 3.10で導入されたmatch文は、値のパターンに基づく分岐を可能にする[4]。単純なswitch-case以上の強力なパターンマッチング機能を提供する。

3.1 基本的なパターン:リテラル、変数、ワイルドカードによるマッチング。

def http_status(status: int) -> str:
    match status:
        case 200:
            return "OK"
        case 404:
            return "Not Found"
        case 500:
            return "Internal Server Error"
        case _:
            return f"Unknown status: {status}"

print(http_status(200))  # OK
print(http_status(999))  # Unknown status: 999

3.2 構造パターン:シーケンス、マッピング、クラスの構造にマッチできる。

# シーケンスパターン
def describe_list(items: list) -> str:
    match items:
        case []:
            return "empty"
        case [x]:
            return f"single: {x}"
        case [x, y]:
            return f"pair: {x}, {y}"
        case [first, *rest]:
            return f"first: {first}, rest: {rest}"

print(describe_list([1, 2, 3, 4]))  # first: 1, rest: [2, 3, 4]

# マッピングパターン
def process_event(event: dict) -> str:
    match event:
        case {"type": "click", "x": x, "y": y}:
            return f"Click at ({x}, {y})"
        case {"type": "keypress", "key": key}:
            return f"Key pressed: {key}"
        case {"type": type_}:
            return f"Unknown event type: {type_}"
        case _:
            return "Invalid event"

print(process_event({"type": "click", "x": 100, "y": 200}))
# Click at (100, 200)

3.3 クラスパターン:dataclassやNamedTupleとの組み合わせが強力。

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

@dataclass
class Circle:
    center: Point
    radius: float

@dataclass
class Rectangle:
    top_left: Point
    width: float
    height: float

def area(shape) -> float:
    match shape:
        case Circle(center=_, radius=r):
            return 3.14159 * r * r
        case Rectangle(top_left=_, width=w, height=h):
            return w * h
        case _:
            raise ValueError("Unknown shape")

print(area(Circle(Point(0, 0), 5)))  # 78.53975
print(area(Rectangle(Point(0, 0), 10, 20)))  # 200

3.4 ガード条件:ifでマッチ条件を追加できる。

def classify_number(n: int) -> str:
    match n:
        case x if x < 0:
            return "negative"
        case 0:
            return "zero"
        case x if x % 2 == 0:
            return "positive even"
        case _:
            return "positive odd"

4. 非同期処理

4.1 async/await

Python 3.5で導入されたasync/await構文は、コルーチンベースの非同期処理を直感的に記述可能にする[5]。I/Oバウンドな処理を効率的に並行実行できる。

import asyncio

async def fetch_data(url: str) -> str:
    """非同期でデータを取得(シミュレーション)"""
    print(f"Fetching {url}...")
    await asyncio.sleep(1)  # I/O待ちをシミュレート
    return f"Data from {url}"

async def main():
    # 順次実行(3秒かかる)
    result1 = await fetch_data("https://api1.example.com")
    result2 = await fetch_data("https://api2.example.com")
    result3 = await fetch_data("https://api3.example.com")
    
    # 並行実行(1秒で完了)
    results = await asyncio.gather(
        fetch_data("https://api1.example.com"),
        fetch_data("https://api2.example.com"),
        fetch_data("https://api3.example.com"),
    )
    print(results)

asyncio.run(main())

Fig. 1に同期処理と非同期処理の比較を示す。

4.2 asyncio

4.2.1 主要な関数:Table 4にasyncioの主要関数を示す。

Table 4. asyncioの主要関数

関数 用途
asyncio.run() エントリーポイントからコルーチンを実行
asyncio.gather() 複数コルーチンを並行実行し、全結果を取得
asyncio.create_task() コルーチンをTaskとしてスケジュール
asyncio.wait_for() タイムアウト付きで待機
asyncio.sleep() 非同期スリープ
asyncio.Queue() 非同期キュー

4.2.2 タスクのキャンセルとタイムアウト

import asyncio

async def long_running_task():
    try:
        await asyncio.sleep(10)
        return "completed"
    except asyncio.CancelledError:
        print("Task was cancelled")
        raise

async def main():
    # タイムアウト付き実行
    try:
        result = await asyncio.wait_for(
            long_running_task(),
            timeout=2.0
        )
    except asyncio.TimeoutError:
        print("Task timed out")

    # 手動キャンセル
    task = asyncio.create_task(long_running_task())
    await asyncio.sleep(1)
    task.cancel()
    try:
        await task
    except asyncio.CancelledError:
        print("Caught cancellation")

asyncio.run(main())

4.2.3 非同期コンテキストマネージャとイテレータ

import asyncio
from typing import AsyncIterator

# 非同期コンテキストマネージャ
class AsyncResource:
    async def __aenter__(self):
        print("Acquiring resource")
        await asyncio.sleep(0.1)
        return self
    
    async def __aexit__(self, *args):
        print("Releasing resource")
        await asyncio.sleep(0.1)

# 非同期ジェネレータ
async def async_range(n: int) -> AsyncIterator[int]:
    for i in range(n):
        await asyncio.sleep(0.1)
        yield i

async def main():
    async with AsyncResource() as resource:
        async for i in async_range(5):
            print(i)

asyncio.run(main())

5. Python 3.10-3.13新機能

Table 5に各バージョンの主要な新機能をまとめる。

Table 5. Python 3.10-3.13の主要新機能

バージョン 機能 概要
3.10 match文 構造的パターンマッチング
3.10 X | Y構文 Union型の簡潔な記法
3.10 ParamSpec デコレータの型付け改善
3.11 例外グループ ExceptionGroup, except*
3.11 tomllib TOML パーサー標準搭載
3.11 Self型 メソッドの戻り値型として自クラスを指定
3.12 型パラメータ構文 class C[T]: / def f[T]():
3.12 f-string改善 ネストした引用符、コメント可能
3.13 Free-threaded mode GIL無効化オプション(実験的)
3.13 JIT compiler 実験的JITコンパイラ

5.1 Python 3.11: 例外グループ:複数の例外を同時に扱える。

# 例外グループの発生
def process_items(items):
    errors = []
    for item in items:
        try:
            # 処理
            if item < 0:
                raise ValueError(f"Negative value: {item}")
        except ValueError as e:
            errors.append(e)
    if errors:
        raise ExceptionGroup("Processing errors", errors)

# except*で特定の例外を捕捉
try:
    process_items([1, -2, 3, -4])
except* ValueError as eg:
    print(f"Caught {len(eg.exceptions)} ValueErrors")
except* TypeError as eg:
    print(f"Caught TypeErrors")

5.2 Python 3.12: 型パラメータ構文:ジェネリクスの記述が大幅に簡潔化。

# Python 3.11以前
from typing import TypeVar, Generic

T = TypeVar('T')
K = TypeVar('K')
V = TypeVar('V')

class Container(Generic[T]):
    def __init__(self, value: T) -> None:
        self.value = value

def first[T](items: list[T]) -> T | None:  # エラー

# Python 3.12以降
class Container[T]:
    def __init__(self, value: T) -> None:
        self.value = value

def first[T](items: list[T]) -> T | None:
    return items[0] if items else None

# 型制約も簡潔に
def add[T: (int, float)](a: T, b: T) -> T:
    return a + b

5.3 Python 3.12: f-string改善

# Python 3.11以前: ネストした引用符が難しい
# name = f"User: {user['name']}"  # 異なる引用符を使う必要あり

# Python 3.12以降: 同じ引用符でネスト可能
name = f"User: {user["name"]}"

# 複数行とコメントも可能
result = f"""
    Name: {
        user["name"]  # ユーザー名
    }
    Age: {
        user["age"]   # 年齢
    }
"""

References

[1] G. van Rossum, J. Lehtosalo, Ł. Langa, "PEP 484 -- Type Hints," python.org, 2014.

[2] I. Levkivskyi et al., "PEP 544 -- Protocols: Structural subtyping," python.org, 2017.

[3] E. Smith, "PEP 557 -- Data Classes," python.org, 2017.

[4] B. Cannon, G. van Rossum, T. Wouters, "PEP 634 -- Structural Pattern Matching," python.org, 2020.

[5] Y. Selivanov, "PEP 492 -- Coroutines with async and await syntax," python.org, 2015.

免責事項
本コンテンツは2025年12月時点の情報に基づいて作成されている。Python 3.13の機能は実験的なものを含み、将来のバージョンで変更される可能性がある。最新の情報は公式ドキュメントを参照されたい。

← 前章:言語基礎と設計思想次章:開発環境とツールチェーン →