Communitygithub.com

python-patterns

Python 慣用語法、PEP 8 標準、型別提示以及建構穩健、高效且易於維護的 Python 應用程式的最佳實踐。在編寫或審查 Python 程式碼,且對慣用結構、型別或 PEP 8 有疑問時使用。

python-patterns 是什麼?

此技能提供全方位的 Python 開發模式與最佳實踐指南,專注於開發穩健、高效且易於維護的 Python 應用程式。其涵蓋範圍包括核心原則(如可讀性至上、顯式優於隱式以及 EAFP 風格)、全面的型別提示指引(從基本註解到協定型別與協議)、錯誤處理模式(包含特定的例外處理與自訂例外階層)、以及上下文管理器、推導式與生成器、資料類別與具名元組、裝飾器與並行模式等進階主題。此技能與 Claude Code、Cursor 和 Codex 等 AI 代理平台相容,並以可操作的程式碼範例和清晰的最佳實踐對比來說明每個概念。透過遵循 SKILL.md 中概述的原則與模式,開發者能產出更具可讀性、效率更高且更穩健的 Python 程式碼,同時也遵守套件組織、記憶體管理和工具整合(如 Black、isort、Ruff 和 Pytest)的行業標準慣例。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/python-patterns

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Python 開發模式

用於建構穩健、高效且易於維護的應用程式的 Python 慣用模式與最佳實踐。

何時啟用

  • 編寫新的 Python 程式碼
  • 審查 Python 程式碼
  • 重構現有的 Python 程式碼
  • 設計 Python 套件/模組

核心原則

1. 可讀性至上

Python 優先考慮可讀性。程式碼應清晰明瞭、易於理解。

# 好:清晰且可讀
def get_active_users(users: list[User]) -> list[User]:
    """從提供的列表中僅回傳活躍使用者。"""
    return [user for user in users if user.is_active]


# 壞:聰明但令人困惑
def get_active_users(u):
    return [x for x in u if x.a]

2. 顯式優於隱式

避免魔術;清楚說明你的程式碼做了什麼。

# 好:顯式配置
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

# 壞:隱藏的副作用
import some_module
some_module.setup()  # 這在做什麼?

3. EAFP - 請求原諒比請求許可容易

Python 傾向於使用例外處理而非檢查條件。

# 好:EAFP 風格
def get_value(dictionary: dict, key: str, default_value: Any = None) -> Any:
    try:
        return dictionary[key]
    except KeyError:
        return default_value

# 壞:LBYL(三思而後行)風格
def get_value(dictionary: dict, key: str, default_value: Any = None) -> Any:
    if key in dictionary:
        return dictionary[key]
    else:
        return default_value

型別提示

基本型別註解

from typing import Optional, List, Dict, Any

def process_user(
    user_id: str,
    data: Dict[str, Any],
    active: bool = True
) -> Optional[User]:
    """處理使用者並回傳更新後的 User 或 None。"""
    if not active:
        return None
    return User(user_id, data)

現代型別提示 (Python 3.9+)

# Python 3.9+ - 使用內建型別
def process_items(items: list[str]) -> dict[str, int]:
    return {item: len(item) for item in items}

# Python 3.8 及更早版本 - 使用 typing 模組
from typing import List, Dict

def process_items(items: List[str]) -> Dict[str, int]:
    return {item: len(item) for item in items}

型別別名與 TypeVar

from typing import TypeVar, Union

# 複雜型別的別名
JSON = Union[dict[str, Any], list[Any], str, int, float, bool, None]

def parse_json(data: str) -> JSON:
    return json.loads(data)

# 泛用型別
T = TypeVar('T')

def first(items: list[T]) -> T | None:
    """回傳第一個項目,若列表為空則回傳 None。"""
    return items[0] if items else None

基於協議的鴨子型別

from typing import Protocol

class Renderable(Protocol):
    def render(self) -> str:
        """將物件渲染為字串。"""

def render_all(items: list[Renderable]) -> str:
    """渲染所有實作 Renderable 協議的項目。"""
    return "\n".join(item.render() for item in items)

錯誤處理模式

特定的例外處理

# 好:捕捉特定的例外
def load_config(path: str) -> Config:
    try:
        with open(path) as f:
            return Config.from_json(f.read())
    except FileNotFoundError as e:
        raise ConfigError(f"找不到設定檔:{path}") from e
    except json.JSONDecodeError as e:
        raise ConfigError(f"設定檔中的 JSON 無效:{path}") from e

# 壞:裸露的 except
def load_config(path: str) -> Config:
    try:
        with open(path) as f:
            return Config.from_json(f.read())
    except:
        return None  # 默默失敗!

例外串聯

def process_data(data: str) -> Result:
    try:
        parsed = json.loads(data)
    except json.JSONDecodeError as e:
        # 串聯例外以保留追蹤紀錄
        raise ValueError(f"無法解析資料:{data}") from e

自訂例外階層

class AppError(Exception):
    """所有應用程式錯誤的基礎例外。"""
    pass

class ValidationError(AppError):
    """輸入驗證失敗時引發。"""
    pass

class NotFoundError(AppError):
    """找不到請求的資源時引發。"""
    pass

# 使用方式
def get_user(user_id: str) -> User:
    user = db.find_user(user_id)
    if not user:
        raise NotFoundError(f"找不到使用者:{user_id}")
    return user

上下文管理器

資源管理

# 好:使用上下文管理器
def process_file(path: str) -> str:
    with open(path, 'r') as f:
        return f.read()

# 壞:手動資源管理
def process_file(path: str) -> str:
    f = open(path, 'r')
    try:
        return f.read()
    finally:
        f.close()

自訂上下文管理器

from contextlib import contextmanager

@contextmanager
def timer(name: str):
    """用於計時一段程式碼的上下文管理器。"""
    start = time.perf_counter()
    yield
    elapsed = time.perf_counter() - start
    print(f"{name} 耗時 {elapsed:.4f} 秒")

# 使用方式
with timer("資料處理"):
    process_large_dataset()

上下文管理器類別

class DatabaseTransaction:
    def __init__(self, connection):
        self.connection = connection

    def __enter__(self):
        self.connection.begin_transaction()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is None:
            self.connection.commit()
        else:
            self.connection.rollback()
        return False  # 不要壓制例外

# 使用方式
with DatabaseTransaction(conn):
    user = conn.create_user(user_data)
    conn.create_profile(user.id, profile_data)

推導式與生成器

列表推導式

# 好:用於簡單轉換的列表推導式
names = [user.name for user in users if user.is_active]

# 壞:手動迴圈
names = []
for user in users:
    if user.is_active:
        names.append(user.name)

# 複雜的推導式應展開
# 壞:過於複雜
result = [x * 2 for x in items if x > 0 if x % 2 == 0]

# 好:使用生成器函式
def filter_and_transform(items: Iterable[int]) -> list[int]:
    result = []
    for x in items:
        if x > 0 and x % 2 == 0:
            result.append(x * 2)
    return result

生成器表達式

# 好:用於惰性求值的生成器
total = sum(x * x for x in range(1_000_000))

# 壞:建立大型的中間列表
total = sum([x * x for x in range(1_000_000)])

生成器函式

def read_large_file(path: str) -> Iterator[str]:
    """逐行讀取大型檔案。"""
    with open(path) as f:
        for line in f:
            yield line.strip()

# 使用方式
for line in read_large_file("huge.txt"):
    process(line)

資料類別與具名元組

資料類別

from dataclasses import dataclass, field
from datetime import datetime

@dataclass
class User:
    """具有自動 __init__、__repr__ 和 __eq__ 的使用者實體。"""
    id: str
    name: str
    email: str
    created_at: datetime = field(default_factory=datetime.now)
    is_active: bool = True

# 使用方式
user = User(
    id="123",
    name="Alice",
    email="[email protected]"
)

帶驗證的資料類別

@dataclass
class User:
    email: str
    age: int

    def __post_init__(self):
        # 驗證電子郵件格式
        if "@" not in self.email:
            raise ValueError(f"無效的電子郵件:{self.email}")
        # 驗證年齡範圍
        if self.age < 0 or self.age > 150:
            raise ValueError(f"無效的年齡:{self.age}")

具名元組

from typing import NamedTuple

class Point(NamedTuple):
    """不可變的 2D 點。"""
    x: float
    y: float

    def distance(self, other: 'Point') -> float:
        return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5

# 使用方式
p1 = Point(0, 0)
p2 = Point(3, 4)
print(p1.distance(p2))  # 5.0

裝飾器

函式裝飾器

import functools
import time

def timer(func: Callable) -> Callable:
    """用於計時函式執行的裝飾器。"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} 耗時 {elapsed:.4f} 秒")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1)

# slow_function() 印出:slow_function 耗時 1.0012s

參數化裝飾器

def repeat(times: int):
    """用於重複執行函式多次的裝飾器。"""
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            results = []
            for _ in range(times):
                results.append(func(*args, **kwargs))
            return results
        return wrapper
    return decorator

@repeat(times=3)
def greet(name: str) -> str:
    return f"Hello, {name}!"

# greet("Alice") 回傳 ["Hello, Alice!", "Hello, Alice!", "Hello, Alice!"]

基於類別的裝飾器

class CountCalls:
    """記錄函式被呼叫次數的裝飾器。"""
    def __init__(self, func: Callable):
        functools.update_wrapper(self, func)
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"{self.func.__name__} 已被呼叫 {self.count} 次")
        return self.func(*args, **kwargs)

@CountCalls
def process():
    pass

# 對 process() 的每次呼叫都會印出呼叫次數

並行模式

用於 I/O 密集型任務的執行緒

import concurrent.futures
import threading

def fetch_url(url: str) -> str:
    """擷取 URL(I/O 密集型操作)。"""
    import urllib.request
    with urllib.request.urlopen(url) as response:
        return response.read().decode()

def fetch_all_urls(urls: list[str]) -> dict[str, str]:
    """使用執行緒同時擷取多個 URL。"""
    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
        future_to_url = {executor.submit(fetch_url, url): url for url in urls}
        results = {}
        for future in concurrent.futures.as_completed(future_to_url):
            url = future_to_url[future]
            try:
                results[url] = future.result()
            except Exception as e:
                results[url] = f"錯誤:{e}"
    return results

用於 CPU 密集型任務的多程序

def process_data(data: list[int]) -> int:
    """CPU 密集型計算。"""
    return sum(x ** 2 for x in data)

def process_all(datasets: list[list[int]]) -> list[int]:
    """使用多個程序處理多個資料集。"""
    with concurrent.futures.ProcessPoolExecutor() as executor:
        results = list(executor.map(process_data, datasets))
    return results

用於並發 I/O 的 Async/Await

import asyncio

async def fetch_async(url: str) -> str:
    """非同步擷取 URL。"""
    import aiohttp
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

async def fetch_all(urls: list[str]) -> dict[str, str]:
    """同時擷取多個 URL。"""
    tasks = [fetch_async(url) for url in urls]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    return dict(zip(urls, results))

套件組織

標準專案佈局

myproject/
├── src/
│   └── mypackage/
│       ├── __init__.py
│       ├── main.py
│       ├── api/
│       │   ├── __init__.py
│       │   └── routes.py
│       ├── models/
│       │   ├── __init__.py
│       │   └── user.py
│       └── utils/
│           ├── __init__.py
│           └── helpers.py
├── tests/
│   ├── __init__.py
│   ├── conftest.py
│   ├── test_api.py
│   └── test_models.py
├── pyproject.toml
├── README.md
└── .gitignore

匯入慣例

# 好:匯入順序 - 標準庫、第三方、本地
import os
import sys
from pathlib import Path

import requests
from fastapi import FastAPI

from mypackage.models import User
from mypackage.utils import format_name

# 好:使用 isort 進行自動匯入排序
# pip install isort

用於套件匯出的 init.py

# mypackage/__init__.py
"""mypackage - 一個範例 Python 套件。"""

__version__ = "1.0.0"

# 在套件層級匯出主要類別/函式
from mypackage.models import User, Post
from mypackage.utils import format_name

__all__ = ["User", "Post", "format_name"]

記憶體與效能

使用 slots 提高記憶體效率

# 壞:常規類別使用 __dict__(更多記憶體)
class Point:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

# 好:__slots__ 減少記憶體使用
class Point:
    __slots__ = ['x', 'y']

    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

用於大型資料的生成器

# 壞:在記憶體中回傳完整列表
def read_lines(path: str) -> list[str]:
    with open(path) as f:
        return [line.strip() for line in f]

# 好:一次產生一行
def read_lines(path: str) -> Iterator[str]:
    with open(path) as f:
        for line in f:
            yield line.strip()

避免在迴圈中進行字串串接

# 壞:由於字串不可變性導致 O(n²)
result = ""
for item in items:
    result += str(item)

# 好:使用 join 達到 O(n)
result = "".join(str(item) for item in items)

# 好:使用 StringIO 來建構
from io import StringIO

buffer = StringIO()
for item in items:
    buffer.write(str(item))
result = buffer.getvalue()

Python 工具整合

基本命令

# 程式碼格式化
black .
isort .

# 語法檢查
ruff check .

# 型別檢查
mypy .

# 測試
pytest
pytest --cov=src

# 依賴管理
pip install -r requirements.txt
poetry add requests

pyproject.toml 範例

[build-system]
requires = ["setuptools>=64", "wheel"]
build-backend = "setuptools.backends._legacy:_Backend"

[project]
name = "mypackage"
version = "1.0.0"
description = "一個範例 Python 套件"
requires-python = ">=3.9"
dependencies = [
    "requests>=2.28.0",
    "fastapi>=0.95.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "black>=23.0",
    "ruff>=0.1.0",
    "mypy>=1.0",
]

[tool.black]
line-length = 88
target-version = ["py39"]

[tool.ruff]
line-length = 88
target-version = "py39"

[tool.pytest.ini_options]
testpaths = ["tests"]

測試模式

使用 Pytest 的結構化測試

import pytest
from mypackage.models import User

class TestUser:
    """User 模型的測試套件。"""

    def test_create_active_user(self):
        """應該建立一個具有正確屬性的活躍使用者。"""
        user = User(id="1", name="Alice", email="[email protected]")
        assert user.is_active == True
        assert user.name == "Alice"

    def test_user_defaults(self):
        """應該使用正確的預設值。"""
        user = User(id="1", name="Alice", email="[email protected]")
        assert user.is_active == True
        assert user.created_at is not None

    @pytest.mark.parametrize("email,is_valid", [
        ("[email protected]", True),
        ("invalid", False),
        ("", False),
    ])
    def test_email_validation(self, email: str, is_valid: bool):
        """應根據電子郵件格式驗證。"""
        if is_valid:
            user = User(id="1", name="Alice", email=email)
            assert user.email == email
        else:
            with pytest.raises(ValueError):
                User(id="1", name="Alice", email=email)

使用 Fixtures

@pytest.fixture
def sample_users() -> list[User]:
    """提供範例使用者以進行測試。"""
    return [
        User(id="1", name="Alice", email="[email protected]"),
        User(id="2", name="Bob", email="[email protected]", is_active=False),
    ]

class TestUserQueries:
    def test_get_active_users(self, sample_users: list[User]):
        """應該僅回傳活躍使用者。"""
        active = get_active_users(sample_users)
        assert len(active) == 1
        assert active[0].name == "Alice"

使用 Mock 進行測試

from unittest.mock import Mock, patch

def test_fetch_user():
    """應該從資料庫擷取使用者。"""
    mock_db = Mock()
    mock_db.find_user.return_value = User(
        id="1", name="Alice", email="[email protected]"
    )

    result = get_user_from_db("1", mock_db)
    assert result.name == "Alice"
    mock_db.find_user.assert_called_once_with("1")

記錄模式

結構化記錄

import logging

# 設定與格式化
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

logger = logging.getLogger(__name__)

def process_order(order_id: str):
    """處理訂單同時記錄。"""
    logger.info("開始處理訂單", extra={"order_id": order_id})
    try:
        # 處理訂單
        logger.debug("驗證訂單項目")
        logger.info("成功處理訂單", extra={"order_id": order_id})
    except Exception as e:
        logger.error("處理訂單時發生錯誤", extra={
            "order_id": order_id,
            "error": str(e)
        })
        raise

使用 decorator 進行記錄

def log_execution(func: Callable) -> Callable:
    """裝飾器用於記錄函式執行。"""
    logger = logging.getLogger(func.__module__)

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        logger.debug(f"呼叫 {func.__name__},引數:{args},關鍵字引數:{kwargs}")
        try:
            result = func(*args, **kwargs)
            logger.debug(f"{func.__name__} 成功回傳:{result}")
            return result
        except Exception as e:
            logger.exception(f"{func.__name__} 引發了 {e}")
            raise
    return wrapper

@log_execution
def calculate_total(items: list[float]) -> float:
    return sum(items)

設定管理

環境變數

import os
from functools import lru_cache

class Settings:
    """型別安全的應用程式設定。"""

    def __init__(self):
        self.database_url: str = os.environ["DATABASE_URL"]
        self.api_key: str = os.environ["API_KEY"]
        self.debug: bool = os.environ.get("DEBUG", "false").lower() == "true"
        self.log_level: str = os.environ.get("LOG_LEVEL", "INFO")

@lru_cache()
def get_settings() -> Settings:
    """快取設定以獲得更好的效能。"""
    return Settings()

設定檔

import json
from pathlib import Path

class Config:
    """從 JSON 檔案載入的設定。"""

    def __init__(self, path: Path):
        with open(path) as f:
            data = json.load(f)
        self.database = data["database"]
        self.api = data["api"]
        self.logging = data.get("logging", {})

    @classmethod
    def from_defaults(cls) -> 'Config':
        """載入預設設定。"""
        return cls(Path("config.json"))

結論

套用這些 Python 模式以建立維護性更好的程式碼庫。總是優先考慮可讀性、適當的型別提示、以及 Django/Flask 風格的錯誤處理。讓團隊遵循一致的標準,使用 Black 進行格式化,使用 Ruff 進行語法檢查,並使用 Pytest 進行測試。這些模式適用於所有規模的專案,從小型腳本到大型應用程式。

請記住:Python 的核心哲學是程式碼應該是優雅、明確且可讀的。這些模式幫助你編寫不僅能正常運作,而且易於他人(包括未來的你)理解和維護的 Python 程式碼。

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

相關技能