async-python-patterns について
asyncio、並行処理、async/await を使った Python 非同期開発の実践ガイドです。I/O 中心のアプリを開発する人向けに、パターンやテスト、よくある問題を解説します。
主な機能
- 01イベントループ、コルーチン、タスク、Future の解説
- 02並行実行とタスク管理のパターン
- 03エラー、タイムアウト、キャンセル処理
- 04同期・非同期の選択指針
- 05非同期テストの例
活用シーン
- 01並行処理を行う Web API の構築
- 02データベースとネットワーク I/O の並行実行
- 03並行型 Web スクレイパーの作成
- 04WebSocket またはバックグラウンドタスクの開発
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wshobson/ agents --skill async-python-patternsインストール後、Claude Code で/async-python-patternsを入力して読み込まれたか確認します。
同梱ファイルreferences/details.md
詳細
非同期 Python パターン
asyncio、並行プログラミングパターン、async/await を用いて、高性能でノンブロッキングなシステムを構築するための非同期 Python アプリケーション実装に関する包括的なガイダンスです。
このスキルを使用する場面
- 非同期 Web API の構築 (FastAPI、aiohttp、Sanic)
- 並行 I/O 操作の実装 (データベース、ファイル、ネットワーク)
- 並行リクエストを行う Web スクレイパーの作成
- リアルタイムアプリケーションの開発 (WebSocket サーバー、チャットシステム)
- 複数の独立したタスクの同時処理
- 非同期通信によるマイクロサービスの構築
- I/O バウンドなワークロードの最適化
- 非同期バックグラウンドタスクとキューの実装
同期 vs 非同期の判断ガイド
非同期を採用する前に、それがユースケースに適した選択かどうかを検討してください。
| ユースケース | 推奨されるアプローチ |
|---|---|
| 多数の並行ネットワーク/DB 呼び出し | asyncio |
| CPU バウンドの計算 | multiprocessing またはスレッドプール |
| I/O + CPU の混在 | asyncio.to_thread() で CPU 処理をオフロード |
| シンプルなスクリプト、接続数が少ない | 同期 (よりシンプルでデバッグが容易) |
| 高並行性の Web API | 非同期フレームワーク (FastAPI、aiohttp) |
重要なルール: 呼び出しパス内では、完全に同期か完全に非同期のいずれかにしてください。混在させると、隠れたブロッキングと複雑さを生み出します。
中核となる概念
1. イベントループ
イベントループは asyncio の心臓部であり、非同期タスクを管理およびスケジューリングします。
主な特性:
- シングルスレッドの協調的マルチタスキング
- コルーチンを実行するようスケジューリング
- ブロッキングせずに I/O 操作を処理
- コールバックとフューチャーを管理
2. コルーチン
async def で定義され、一時停止と再開が可能な関数です。
構文:
async def my_coroutine():
result = await some_async_operation()
return result
3. タスク
イベントループ上で並行して実行される、スケジューリングされたコルーチンです。
4. フューチャー
非同期操作の最終的な結果を表す低レベルオブジェクトです。
5. 非同期コンテキストマネージャー
適切なクリーンアップのために async with をサポートするリソースです。
6. 非同期イテレーター
非同期データソースを反復処理するために async for をサポートするオブジェクトです。
クイックスタート
import asyncio
async def main():
print("Hello")
await asyncio.sleep(1)
print("World")
# Python 3.7+
asyncio.run(main())
基本的なパターン
パターン 1: 基本的な Async/Await
import asyncio
async def fetch_data(url: str) -> dict:
"""Fetch data from URL asynchronously."""
await asyncio.sleep(1) # Simulate I/O
return {"url": url, "data": "result"}
async def main():
result = await fetch_data("https://api.example.com")
print(result)
asyncio.run(main())
パターン 2: gather() による並行実行
import asyncio
from typing import List
async def fetch_user(user_id: int) -> dict:
"""Fetch user data."""
await asyncio.sleep(0.5)
return {"id": user_id, "name": f"User {user_id}"}
async def fetch_all_users(user_ids: List[int]) -> List[dict]:
"""Fetch multiple users concurrently."""
tasks = [fetch_user(uid) for uid in user_ids]
results = await asyncio.gather(*tasks)
return results
async def main():
user_ids = [1, 2, 3, 4, 5]
users = await fetch_all_users(user_ids)
print(f"Fetched {len(users)} users")
asyncio.run(main())
パターン 3: タスクの作成と管理
import asyncio
async def background_task(name: str, delay: int):
"""Long-running background task."""
print(f"{name} started")
await asyncio.sleep(delay)
print(f"{name} completed")
return f"Result from {name}"
async def main():
# Create tasks
task1 = asyncio.create_task(background_task("Task 1", 2))
task2 = asyncio.create_task(background_task("Task 2", 1))
# Do other work
print("Main: doing other work")
await asyncio.sleep(0.5)
# Wait for tasks
result1 = await task1
result2 = await task2
print(f"Results: {result1}, {result2}")
asyncio.run(main())
パターン 4: 非同期コードでのエラーハンドリング
import asyncio
from typing import List, Optional
async def risky_operation(item_id: int) -> dict:
"""Operation that might fail."""
await asyncio.sleep(0.1)
if item_id % 3 == 0:
raise ValueError(f"Item {item_id} failed")
return {"id": item_id, "status": "success"}
async def safe_operation(item_id: int) -> Optional[dict]:
"""Wrapper with error handling."""
try:
return await risky_operation(item_id)
except ValueError as e:
print(f"Error: {e}")
return None
async def process_items(item_ids: List[int]):
"""Process multiple items with error handling."""
tasks = [safe_operation(iid) for iid in item_ids]
results = await asyncio.gather(*tasks, return_exceptions=True)
# Filter out failures
successful = [r for r in results if r is not None and not isinstance(r, Exception)]
failed = [r for r in results if r is None or isinstance(r, Exception)]
print(f"Success: {len(successful)}, Failed: {len(failed)}")
return successful
asyncio.run(process_items([1, 2, 3, 4, 5, 6]))
パターン 5: タイムアウト処理
import asyncio
async def slow_operation(delay: int) -> str:
"""Operation that takes time."""
await asyncio.sleep(delay)
return f"Completed after {delay}s"
async def with_timeout():
"""Execute operation with timeout."""
try:
result = await asyncio.wait_for(slow_operation(5), timeout=2.0)
print(result)
except asyncio.TimeoutError:
print("Operation timed out")
asyncio.run(with_timeout())
詳細な実例とパターン
詳細なセクション (## Advanced Patterns で始まるもの) は references/details.md にあります。上記のナビゲーション概要では不十分な場合に、そのファイルをお読みください。
よくある落とし穴
1. await を忘れる
# Wrong - returns coroutine object, doesn't execute
result = async_function()
# Correct
result = await async_function()
2. イベントループのブロッキング
# Wrong - blocks event loop
import time
async def bad():
time.sleep(1) # Blocks!
# Correct
async def good():
await asyncio.sleep(1) # Non-blocking
3. キャンセルを処理しない
async def cancelable_task():
"""Task that handles cancellation."""
try:
while True:
await asyncio.sleep(1)
print("Working...")
except asyncio.CancelledError:
print("Task cancelled, cleaning up...")
# Perform cleanup
raise # Re-raise to propagate cancellation
4. 同期コードと非同期コードの混在
# Wrong - can't call async from sync directly
def sync_function():
result = await async_function() # SyntaxError!
# Correct
def sync_function():
result = asyncio.run(async_function())
非同期コードのテスト
import asyncio
import pytest
# Using pytest-asyncio
@pytest.mark.asyncio
async def test_async_function():
"""Test async function."""
result = await fetch_data("https://api.example.com")
assert result is not None
@pytest.mark.asyncio
async def test_with_timeout():
"""Test with timeout."""
with pytest.raises(asyncio.TimeoutError):
await asyncio.wait_for(slow_operation(5), timeout=1.0)