--- name: python-patterns description: Pythonic 語法習慣、PEP 8 標準、型別提示 (Type Hints) 以及建構強韌、高效且易於維護的 Python 應用程式之最佳實踐。 --- # Python 開發模式 (Python Development Patterns) 用於建構強韌、高效且易於維護的應用程式之道地 Python 模式與最佳實踐。 ## 何時啟用 - 編寫新的 Python 程式碼。 - 審查 Python 程式碼。 - 重構現有的 Python 程式碼。 - 設計 Python 套件或模組。 ## 核心原則 ### 1. 可讀性至上 (Readability Counts) Python 優先考慮可讀性。程式碼應當顯而易見且易於理解。 ```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. 明示優於暗示 (Explicit is Better Than Implicit) 避免使用神祕的機制;應明確表達程式碼的功能。 ```python # 正確:明示配置資訊 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 傾向於使用例外處理,而非預先檢查條件。 ```python # 正確:EAFP 風格 def get_value(dictionary: dict, key: str) -> Any: try: return dictionary[key] except KeyError: return default_value # 錯誤:LBYL (三思而後行) 風格 def get_value(dictionary: dict, key: str) -> Any: if key in dictionary: return dictionary[key] else: return default_value ``` ## 型別提示 (Type Hints) ### 基礎型別註解 ```python from typing import Optional, List, Dict, Any def process_user( user_id: str, data: Dict[str, Any], active: bool = True ) -> Optional[User]: """處理使用者並回傳更新後的使用者物件或 None。""" if not active: return None return User(user_id, data) ``` ### 現代型別提示 (Python 3.9+) ```python # 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 ```python 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 ``` ### 基於 Protocol 的鴨子型別 (Duck Typing) ```python 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) ``` ## 例外處理模式 ### 具體的例外處理 ```python # 正確:捕捉特定的例外 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 # 靜默失敗! ``` ### 例外鏈 (Exception Chaining) ```python def process_data(data: str) -> Result: try: parsed = json.loads(data) except json.JSONDecodeError as e: # 使用 'from e' 鏈結例外以保留堆疊追蹤資訊 raise ValueError(f"解析資料失敗: {data}") from e ``` ### 自定義例外階層 ```python 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 ``` ## 上下文管理器 (Context Managers) ### 資源管理 ```python # 正確:使用上下文管理器 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() ``` ### 自定義上下文管理器 ```python from contextlib import contextmanager import time @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() ``` ## 解析式 (Comprehensions) 與產生器 (Generators) ### 清單解析式 ```python # 正確:使用清單解析式進行簡易轉換 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 ``` ### 產生器表達式 ```python # 正確:使用產生器進行惰性求值 total = sum(x * x for x in range(1_000_000)) # 錯誤:建立了一個巨大的中間清單 total = sum([x * x for x in range(1_000_000)]) ``` ### 產生器函式 ```python 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) ``` ## 資料類別 (Data Classes) 與具名元組 (Named Tuples) ### 資料類別 (Data Classes) ```python 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="alice@example.com" ) ``` ### 具名元組 (Named Tuples) ```python 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 ``` ## 裝飾器 (Decorators) ### 函式裝飾器 ```python import functools import time from typing import Callable 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 ``` ## 並發模式 (Concurrency Patterns) ### I/O 密集型任務使用執行緒 (Threading) ```python import concurrent.futures 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 密集型任務使用多進程 (Multiprocessing) ```python 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 ``` ## 專案與套件組織 ### 標準專案佈局 ``` 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 Conventions) ```python # 正确:匯入順序 — 標準函式庫、第三方套件、本地模組 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 ``` ## 記憶體與效能 ### 使用 __slots__ 提升記憶體效率 ```python # 錯誤:一般類別使用 __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 ``` ### 避免在迴圈中進行字串拼接 ```python # 錯誤:字串不可變性導致 O(n²) 複雜度 result = "" for item in items: result += str(item) # 正確:使用 join 達成 O(n) 複雜度 result = "".join(str(item) for item in items) ``` ## Python 工具整合 ### 必要指令 ```bash # 程式碼格式化 black . isort . # 靜態分析 (Linter) ruff check . pylint mypackage/ # 型別檢查 mypy . # 執行測試 pytest --cov=mypackage --cov-report=html # 安全性掃描 bandit -r . ``` ## 常見語法習慣 (Idioms) 快速參考 | 語法習慣 | 說明描述 | |-------|-------------| | **EAFP** | 請求原諒比請求許可更容易 | | **上下文管理器** | 使用 `with` 語句管理資源 | | **清單解析式** | 用於簡易的資料轉換 | | **產生器** | 用於惰性求值與大型資料集處理 | | **型別提示** | 註解函式簽名以提升安全性 | | **資料類別** | 提供自動生成方法的資料容器 | | **__slots__** | 優化物件記憶體占用 | | **f-strings** | 引進自 Python 3.6 的強化字串格式化 | | **pathlib.Path** | 引進自 Python 3.4 的物件導向路徑操作 | | **enumerate** | 在迴圈中同時獲取索引與元素 | ## 應避免的反模式 ```python # 錯誤:可變物件作為預設參數 (Mutable default arguments) def append_to(item, items=[]): items.append(item) return items # 正確:使用 None 並在函式內部建立新清單 def append_to(item, items=None): if items is None: items = [] items.append(item) return items # 錯誤:使用 type() 檢查型別 if type(obj) == list: process(obj) # 正確:使用 isinstance if isinstance(obj, list): process(obj) # 錯誤:使用 == 與 None 進行比較 if value == None: process() # 正確:使用 is 判斷身分 if value is None: process() # 錯誤:使用星號匯入 (import *) from os.path import * # 正確:明確匯入所需項目 from os.path import join, exists ``` **請記住**:Python 程式碼應具備可讀性、明確性,並遵循「最小驚訝原則」。當遇到疑慮時,應優先考慮清晰度而非技術上的取巧。