Python Modern

👤 Chrisx 📦 v1.0.0 ⭐ 4.4 ⬇️ 550 下載
💻 開發程式設計 免費

📖 技能介紹


name: python-modern description: Python 現代編碼規範(基於專案 Python 版本自動適配) compatibility: 需要 Python 專案(pyproject.toml / setup.cfg / Pipfile / .python-version)。


Python 現代編碼規範

檢測到的 Python 版本

!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
  • docstring 格式(sphinx-notypes):使用 :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 規則:條件分支 ≥ 4 個時必須使用 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()

各版本特性

Python 3.6+

  • f-string:使用 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 中使用 yield
async def async_counter(n: int):
    for i in range(n):
        await asyncio.sleep(0.1)
        yield i

Python 3.7+

  • dataclasses:使用 @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: float
  • dict 保序保證:從 Python 3.7 起,dict 官方保證插入順序。不需要使用 collections.OrderedDict 來維護順序。
# 不要這樣寫(除非需要 OrderedDict 的特殊比較語義):
from collections import OrderedDict
d = OrderedDict()
# 應該這樣寫:
d = {}
  • breakpoint():使用內建的 breakpoint() 代替手動匯入 pdb
# 不要這樣寫:
import pdb; pdb.set_trace()
# 應該這樣寫:
breakpoint()

Python 3.8+

  • walrus 運算子 :=:在表示式中賦值,減少重複計算
# 不要這樣寫:
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-string = 除錯輸出f"{expr=}" 自動顯示錶達式和其值,除錯時非常實用
# 不要這樣寫:
print(f"user={user}, count={count}")
# 應該這樣寫:
print(f"{user=}, {count=}")  # 輸出: user='alice', count=42

Python 3.9+

  • 內建泛型型別:使用 list[int]dict[str, int]tuple[int, ...] 代替 typing.Listtyping.Dicttyping.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")

Python 3.10+

  • 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:捕獲可呼叫物件的引數型別,用於裝飾器

    發現更多技能外掛,請訪問7w4.net。

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):
    ...

Python 3.11+

  • ExceptionGroupexcept*:併發任務中同時處理多個異常
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]

Python 3.12+

  • 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: ...
  • f-string 巢狀引號:f-string 內部可使用與外部相同的引號
# 不要這樣寫(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:
        ...

Python 3.13+

  • warnings.deprecated:標準的棄用裝飾器,統一棄用警告
from warnings import deprecated

@deprecated("Use new_function() instead")
def old_function():
    ...
  • 改進的錯誤資訊:Python 3.13 對 NameErrorImportError 等提供更精確的錯誤建議。這不影響編碼方式,但有助於除錯。

  • 新的 REPL:支援多行編輯、彩色輸出。這不影響編碼方式。

🤖 AI 評測

這是一份相當實用的 Python 編碼規範參考指南。它能根據專案自動識別 Python 版本並給出對應的現代化寫法,省去了記憶各版本特性的麻煩。規範示例清晰,對比明確,容易理解和執行。美中不足的是內容覆蓋面可以更廣,比如測試、效能等方面的最佳實踐目前缺失。總體來說質量不錯,是 Python 開發者值得收藏的實用工具。

📊 多維度評分

適應性4.3
規範性4.5
有效性4.9
可靠性3.7
可信度4.8

📁 包含檔案 (1 個)

📄 SKILL.md 15.4 KB