name: python-modern description: Python 現代編碼規範(基於專案 Python 版本自動適配) compatibility: 需要 Python 專案(pyproject.toml / setup.cfg / Pipfile / .python-version)。
!PY_VER=""; if [ -f pyproject.toml ]; then PY_VER=$(grep -i 'requires-python' pyproject.toml 2>/dev/null | head -1 | sed 's/^[^=]*=[ ]*//' | tr -d "\"' "); elif [ -f setup.cfg ]; then PY_VER=$(grep -i 'python_requires' setup.cfg 2>/dev/null | head -1 | sed 's/^[^=]*=[ ]*//' | tr -d ' '); elif [ -f Pipfile ]; then PY_VER=$(grep -i 'python_version' Pipfile 2>/dev/null | head -1 | sed 's/^[^=]*=[ ]*//' | tr -d "\"' "); fi; if [ -z "$PY_VER" ] && [ -f setup.py ]; then PY_VER=$(grep -oE "python_requires\s*=\s*[\"'][^\"']+[\"']" setup.py 2>/dev/null | head -1 | sed 's/python_requires\s*=\s*//' | tr -d "\"' "); fi; if [ -z "$PY_VER" ] && [ -f .python-version ]; then PY_VER=$(head -1 .python-version 2>/dev/null | tr -d ' '); fi; if [ -z "$PY_VER" ]; then echo "unknown"; elif echo "$PY_VER" | grep -qE '^==[0-9]+\.[0-9]+'; then echo "$PY_VER" | sed 's/^==//'; elif echo "$PY_VER" | grep -qE '<[=]?[0-9]+\.[0-9]+'; then UB=$(echo "$PY_VER" | grep -oE '<[=]?[0-9]+\.[0-9]+' | head -1); if echo "$UB" | grep -q '<='; then echo "$UB" | sed 's/<=//'; else MAJ=$(echo "$UB" | sed 's/<//;s/\..*//' ); MIN=$(echo "$UB" | sed 's/<//;s/[0-9]*\.//' ); echo "${MAJ}.$((MIN - 1))"; fi; elif echo "$PY_VER" | grep -qE '^>=?[0-9]+\.[0-9]+$'; then ENV_PY=""; for cmd in .venv/bin/python venv/bin/python python3 python; do if V=$($cmd --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+' | head -1) && [ -n "$V" ]; then ENV_PY="$V"; break; fi; done; echo "${ENV_PY:-3.12}"; elif echo "$PY_VER" | grep -qE '^[0-9]+\.[0-9]+'; then echo "$PY_VER" | grep -oE '^[0-9]+\.[0-9]+'; else echo "unknown"; fi
不要自行搜尋版本配置檔案或嘗試檢測版本,僅使用上方顯示的版本。
如果檢測到版本(非 "unknown"): - 回覆:"當前專案使用 Python X.Y,將嚴格遵循該版本及之前的現代 Python 最佳實踐。如需指定其他目標版本,請告知。" - 不要逐一列舉特性,不要要求確認
如果版本為 "unknown": - 回覆:"無法在當前倉庫中檢測到 Python 版本" - 使用 AskUserQuestion 詢問:"應該以哪個 Python 版本為目標?" → [3.8] / [3.9] / [3.10] / [3.11] / [3.12] / [3.13]
編寫 Python 程式碼時,使用本文件中目標版本及之前的所有特性: - 優先使用現代語法和標準庫代替舊模式 - 不要使用高於目標版本的特性 - 當存在現代替代方案時,不要使用過時模式
通用最佳實踐(與版本無關):
- 所有 import 必須放在檔案頂部,不要在函式或條件分支內延遲匯入。頂部匯入能確保缺失依賴在啟動/部署階段立即暴露,而非在線上執行到特定程式碼路徑時才觸發 ImportError。僅在以下情況例外:
- 可選依賴(try: import xxx except ImportError: xxx = None)
- 解決迴圈匯入(但應優先考慮重構模組結構)
- 不要使用 from __future__ import annotations。它將註解變為字串延遲求值,會破壞 Pydantic、FastAPI、dataclasses 等依賴執行時註解解析的庫。PEP 563 已被 PEP 649 取代,這是一條廢棄路線。需要前向引用時使用引號字串 "ClassName" 代替。
- 變數型別註解強制:所有模組級和類級變數必須顯式宣告型別註解,禁止僅靠命名或註釋傳遞型別資訊。
# 禁止
count = 0
name = ""
items = []
# 正確
count: int = 0
name: str = ""
items: list[str] = []
-> None 也必須顯式寫出;多返回值使用 tuple 型別。# 禁止(缺少型別註解)
def process(name, count):
return name * count
# 正確
def process(name: str, count: int) -> str:
return name * count
# 顯式寫 -> None(不可省略)
def setup() -> None:
...
# 多返回值(Python 3.9+ 用內建 tuple,低版本用 typing.Tuple)
def fetch_page(page: int) -> tuple[list[str], int]:
...
return items, total
:param name: 描述 和 :return: 描述 格式,禁止 :type / :rtype:(型別已在簽名中宣告,無需重複)。# 正確
def process_text(text: str) -> str:
"""處理並返回字串。
:param text: 原始輸入字串
:return: 處理後的字串
"""
...
def fetch_users(page: int, size: int) -> tuple[list[str], int]:
"""分頁獲取使用者列表。
:param page: 頁碼,從 1 開始
:param size: 每頁條數
:return: (items 列表, 總條數)
"""
...
# 禁止
def fetch_users(page, size):
"""
:type page: int # 禁止:型別已在簽名中
:type size: int # 禁止
:rtype: tuple # 禁止:返回型別已在簽名中
"""
...
match/case(Python 3.10+,低版本專案豁免);3 個及以下可使用 if/elif。# 正確(≥ 4 個分支,Python 3.10+)
match status:
case "pending":
handle_pending()
case "running":
handle_running()
case "done":
handle_done()
case "failed":
handle_failed()
case _:
handle_unknown()
# 禁止(≥ 4 個分支不用 match-case)
if status == "pending":
handle_pending()
elif status == "running":
handle_running()
elif status == "done":
handle_done()
elif status == "failed":
handle_failed()
else:
handle_unknown()
# 允許(3 個及以下分支可用 if/elif)
if role == "admin":
grant_admin()
elif role == "editor":
grant_editor()
else:
grant_viewer()
f"Hello {name}" 代替 "Hello {}".format(name) 或 "Hello %s" % name# 不要這樣寫:
msg = "Hello {}".format(name)
msg = "Hello %s" % name
# 應該這樣寫:
msg = f"Hello {name}"
x: int = 1 代替僅靠註釋說明型別# 不要這樣寫:
x = 1 # type: int
# 應該這樣寫:
x: int = 1
typing 模組中的泛型型別from typing import List, Dict, Optional
def process(items: List[int]) -> Dict[str, int]:
...
def find(name: str) -> Optional[str]:
...
async def 中使用 yieldasync def async_counter(n: int):
for i in range(n):
await asyncio.sleep(0.1)
yield i
@dataclass 代替手寫 __init__、__repr__、__eq__# 不要這樣寫: class Point: def __init__(self, x: float, y: float): self.x = x self.y = y def __repr__(self): return f"Point(x={self.x}, y={self.y})" def __eq__(self, other): return self.x == other.x and self.y == other.y # 應該這樣寫: from dataclasses import dataclass @dataclass class Point: x: float y: float7w4.net提供免費和付費技能下載。
dict 官方保證插入順序。不需要使用 collections.OrderedDict 來維護順序。# 不要這樣寫(除非需要 OrderedDict 的特殊比較語義):
from collections import OrderedDict
d = OrderedDict()
# 應該這樣寫:
d = {}
breakpoint():使用內建的 breakpoint() 代替手動匯入 pdb# 不要這樣寫:
import pdb; pdb.set_trace()
# 應該這樣寫:
breakpoint()
:=:在表示式中賦值,減少重複計算# 不要這樣寫:
line = fp.readline()
while line:
process(line)
line = fp.readline()
# 應該這樣寫:
while (line := fp.readline()):
process(line)
# 不要這樣寫:
match = pattern.search(text)
if match:
handle(match)
# 應該這樣寫:
if (match := pattern.search(text)):
handle(match)
typing.Protocol:定義結構化子型別(鴨子型別的型別化版本)from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None: ...
def render(obj: Drawable) -> None:
obj.draw()
typing.TypedDict:為字典定義精確的鍵值型別from typing import TypedDict
class Movie(TypedDict):
name: str
year: int
/:使用 / 標記僅位置引數,防止呼叫者使用關鍵字def div(a: float, b: float, /) -> float:
return a / b
# div(1, 2) ✓
# div(a=1, b=2) ✗ TypeError
typing.final / @final:標記不可被子類覆寫的方法或不可被繼承的類from typing import final
class Base:
@final
def process(self) -> None:
...
typing.Literal:將引數限定為特定的字面值from typing import Literal
def open_file(mode: Literal["r", "w", "a"]) -> None:
...
= 除錯輸出:f"{expr=}" 自動顯示錶達式和其值,除錯時非常實用# 不要這樣寫:
print(f"user={user}, count={count}")
# 應該這樣寫:
print(f"{user=}, {count=}") # 輸出: user='alice', count=42
list[int]、dict[str, int]、tuple[int, ...] 代替 typing.List、typing.Dict、typing.Tuple# 不要這樣寫:
from typing import List, Dict, Tuple, Set
def process(items: List[int]) -> Dict[str, int]:
...
# 應該這樣寫:
def process(items: list[int]) -> dict[str, int]:
...
str.removeprefix() / str.removesuffix():安全地移除字首/字尾# 不要這樣寫:
if s.startswith("prefix_"):
s = s[len("prefix_"):]
# 應該這樣寫:
s = s.removeprefix("prefix_")
# 不要這樣寫:
if s.endswith("_suffix"):
s = s[:-len("_suffix")]
# 應該這樣寫:
s = s.removesuffix("_suffix")
dict 合併運算子 |:使用 d1 | d2 合併字典# 不要這樣寫:
merged = {**d1, **d2}
# 應該這樣寫:
merged = d1 | d2
# 就地更新:
d1 |= d2 # 代替 d1.update(d2)
zoneinfo 模組:使用標準庫的時區支援代替第三方庫 pytz# 不要這樣寫:
import pytz
tz = pytz.timezone("Asia/Shanghai")
# 應該這樣寫:
from zoneinfo import ZoneInfo
tz = ZoneInfo("Asia/Shanghai")
match/case 結構化模式匹配:代替冗長的 if/elif 鏈# 不要這樣寫:
if command == "quit":
quit_game()
elif command == "go" and direction:
go(direction)
elif command == "get" and item:
get(item)
else:
unknown(command)
# 應該這樣寫:
match command.split():
case ["quit"]:
quit_game()
case ["go", direction]:
go(direction)
case ["get", item]:
get(item)
case _:
unknown(command)
X | Y 聯合型別語法:使用 X | Y 代替 Union[X, Y],使用 X | None 代替 Optional[X]# 不要這樣寫:
from typing import Union, Optional
def process(value: Union[int, str]) -> Optional[str]:
...
# 應該這樣寫:
def process(value: int | str) -> str | None:
...
typing.ParamSpec:捕獲可呼叫物件的引數型別,用於裝飾器from typing import ParamSpec, TypeVar, Callable
P = ParamSpec("P")
R = TypeVar("R")
def log_calls(func: Callable[P, R]) -> Callable[P, R]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
typing.TypeAlias:顯式宣告類型別名# 不要這樣寫(模糊,可能被誤認為是普通賦值):
Vector = list[float]
# 應該這樣寫:
from typing import TypeAlias
Vector: TypeAlias = list[float]
zip(strict=True):要求所有可迭代物件長度相同,不同時丟擲 ValueError# 不要這樣寫(靜默截斷):
for name, score in zip(names, scores):
...
# 應該這樣寫(長度不匹配時立即報錯):
for name, score in zip(names, scores, strict=True):
...
ExceptionGroup 和 except*:併發任務中同時處理多個異常try:
async with asyncio.TaskGroup() as tg:
tg.create_task(task_a())
tg.create_task(task_b())
except* ValueError as eg:
for exc in eg.exceptions:
handle_value_error(exc)
except* TypeError as eg:
for exc in eg.exceptions:
handle_type_error(exc)
typing.Self:在方法中引用當前類的型別# 不要這樣寫:
from typing import TypeVar
T = TypeVar("T", bound="Builder")
class Builder:
def set_name(self: T, name: str) -> T:
self.name = name
return self
# 應該這樣寫:
from typing import Self
class Builder:
def set_name(self, name: str) -> Self:
self.name = name
return self
tomllib:標準庫內建 TOML 解析,無需第三方庫# 不要這樣寫:
import toml # 第三方
config = toml.load("config.toml")
# 應該這樣寫:
import tomllib
with open("config.toml", "rb") as f:
config = tomllib.load(f)
enum.StrEnum:字串列舉,自動使用成員名作為值# 不要這樣寫:
from enum import Enum
class Color(str, Enum):
RED = "RED"
GREEN = "GREEN"
# 應該這樣寫:
from enum import StrEnum
class Color(StrEnum):
RED = "RED"
GREEN = "GREEN"
asyncio.TaskGroup:結構化併發,代替手動管理 create_task + gather# 不要這樣寫:
tasks = [asyncio.create_task(fetch(url)) for url in urls]
results = await asyncio.gather(*tasks)
# 應該這樣寫:
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(fetch(url)) for url in urls]
results = [t.result() for t in tasks]
type 語句:簡潔的類型別名宣告# 不要這樣寫:
from typing import TypeAlias
Vector: TypeAlias = list[float]
# 應該這樣寫:
type Vector = list[float]
[T]:函式和類直接使用泛型引數,無需 TypeVar# 不要這樣寫:
from typing import TypeVar
T = TypeVar("T")
def first(items: list[T]) -> T:
return items[0]
class Stack(Generic[T]):
def push(self, item: T) -> None: ...
# 應該這樣寫:
def first[T](items: list[T]) -> T:
return items[0]
class Stack[T]:
def push(self, item: T) -> None: ...
# 不要這樣寫(Python 3.11 及之前):
msg = f"Hello {d['name']}" # 必須用不同引號
# 應該這樣寫(Python 3.12+):
msg = f"Hello {d["name"]}" # 可以用相同引號
@typing.override:顯式標記方法覆寫父類方法,拼寫錯誤時型別檢查器會報錯from typing import override
class Child(Parent):
@override
def process(self) -> None:
...
warnings.deprecated:標準的棄用裝飾器,統一棄用警告from warnings import deprecated
@deprecated("Use new_function() instead")
def old_function():
...
改進的錯誤資訊:Python 3.13 對 NameError、ImportError 等提供更精確的錯誤建議。這不影響編碼方式,但有助於除錯。
新的 REPL:支援多行編輯、彩色輸出。這不影響編碼方式。
這是一份相當實用的 Python 編碼規範參考指南。它能根據專案自動識別 Python 版本並給出對應的現代化寫法,省去了記憶各版本特性的麻煩。規範示例清晰,對比明確,容易理解和執行。美中不足的是內容覆蓋面可以更廣,比如測試、效能等方面的最佳實踐目前缺失。總體來說質量不錯,是 Python 開發者值得收藏的實用工具。