关于 python-best-practices
python-best-practices 是面向 Python 3.12 及以上代码编写与审查的技能,供需要遵循现代 Python 惯例的编码 Agent 使用。内容涵盖类型标注、异步模式、Pydantic v2、项目配置和标准库选择。
主要功能
- 01采用现代内置类型标注语法
- 02使用 Pydantic v2 API
- 03以 uv 和 pyproject.toml 管理新项目
- 04使用 pathlib 与标准库 tomllib
- 05采用 asyncio.TaskGroup 结构化并发
适用场景
- 01编写符合 Python 3.12+ 的类型化代码
- 02审查并更新旧式 Pydantic 写法
- 03为新项目配置依赖与项目元数据
- 04处理异步任务及 TOML 文件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ofershap/ python-best-practices --skill python-best-practices安装后在 Claude Code 中输入/python-best-practices确认已加载。
详细介绍
适用场景
编写或审查面向 Python 3.12+ 的 Python 代码时,请使用此技能。它要求使用现代类型提示、异步模式、Pydantic v2 API、项目结构和标准库。基于旧代码库训练的 Agent 往往会生成过时的模式;此技能会纠正这些问题。
关键规则
1. 使用现代类型提示语法
错误:
from typing import Union, List, Dict, Optional
def process(items: List[str]) -> Optional[Dict[str, Union[int, str]]]:
...
正确:
def process(items: list[str]) -> dict[str, int | str] | None:
...
原因: list[str] 和 X | Y 是 Python 3.9+ 内置的功能(PEP 585、PEP 604)。导入 typing.List、typing.Union、typing.Optional 来表示内置泛型既冗长,也已被弃用。
2. 使用类型参数语法(PEP 695,Python 3.12+)
错误:
from typing import TypeVar
T = TypeVar("T")
def max(args: list[T]) -> T:
...
正确:
def max[T](args: list[T]) -> T:
...
原因: PEP 695 类型参数更简洁,且作用域局限于函数或类。泛型类也同样如此:使用 class Bag[T]:,而不是 class Bag(Generic[T]):。
3. 使用 match/case 代替 if/elif 链进行模式匹配
错误:
def http_error(status: int) -> str:
if status == 400:
return "Bad request"
elif status == 404:
return "Not found"
elif status == 418:
return "I'm a teapot"
else:
return "Something's wrong"
正确:
def http_error(status: int) -> str:
match status:
case 400:
return "Bad request"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong"
原因: 结构化模式匹配(PEP 634)在处理互斥情况时更清晰,并支持解构。多个字面值可使用 case 401 | 403 | 404:。
4. 使用 Pydantic v2 API,切勿使用 v1
错误:
from pydantic import BaseModel, validator, root_validator
class Model(BaseModel):
x: list[int]
class Config:
validate_assignment = True
@validator("x", each_item=True)
def validate_x(cls, v):
return v * 2
@root_validator
def check_a_b(cls, values):
...
正确:
from pydantic import BaseModel, field_validator, model_validator, ConfigDict
class Model(BaseModel):
model_config = ConfigDict(validate_assignment=True)
x: list[int]
@field_validator("x", mode="each")
@classmethod
def validate_x(cls, v: int) -> int:
return v * 2
@model_validator(mode="after")
def check_a_b(self) -> "Model":
...
return self
原因: Pydantic v1 的装饰器和 class Config 已弃用。v2 使用 model_config、field_validator、model_validator,并显式指定模式。
5. 在新项目中使用 uv 进行包管理
错误:
pip install -r requirements.txt
poetry init
正确:
uv init
uv add requests pydantic
uv sync
原因: uv 速度快,采用现代锁文件,并原生支持 pyproject.toml。新项目优先使用它。
6. 使用 pyproject.toml 配置项目
错误:
# setup.py
from setuptools import setup
setup(name="myapp", version="0.1", ...)
正确:
# pyproject.toml
[project]
name = "myapp"
version = "0.1"
dependencies = ["requests"]
原因: setup.py 和 setup.cfg 已属旧式配置。pyproject.toml(PEP 517/518)是标准做法。
7. 使用 pathlib.Path 代替 os.path
错误:
import os
path = os.path.join(os.getcwd(), "data", "file.txt")
if os.path.exists(path):
with open(path) as f:
...
正确:
from pathlib import Path
path = Path.cwd() / "data" / "file.txt"
if path.exists():
path.read_text()
原因: pathlib 采用面向对象的方式,更清晰且支持跨平台。对于简单的读写,优先使用 Path.read_text()/write_text(),而不是 open()。
8. 使用 f-string 代替 .format() 或 % 格式化
错误:
"User %s has %d items" % (name, count)
"User {} has {} items".format(name, count)
正确:
f"User {name} has {count} items"
原因: f-string 速度更快,也更易读。
9. 使用 dataclass 或 Pydantic 表示结构化数据,不要使用普通 dict
错误:
def get_user() -> dict:
return {"name": "Alice", "age": 30}
正确:
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
def get_user() -> User:
return User(name="Alice", age=30)
原因: 结构化类型可提供类型安全和 IDE 支持。需要验证时使用 Pydantic。
10. 使用 asyncio.TaskGroup 代替 asyncio.gather(Python 3.11+)
错误:
import asyncio
results = await asyncio.gather(f1(), f2(), f3())
正确:
import asyncio
async with asyncio.TaskGroup() as tg:
t1 = tg.create_task(f1())
t2 = tg.create_task(f2())
t3 = tg.create_task(f3())
results = (t1.result(), t2.result(), t3.result())
原因: TaskGroup 能正确传播异常,并在失败时取消其他任务。
11. 使用异常组和 except*(Python 3.11+)
错误:
try:
...
except (ValueError, TypeError) as e:
...
正确(处理并发产生的 ExceptionGroup 时):
try:
...
except* TypeError as e:
print(f"caught {type(e)} with nested {e.exceptions}")
except* OSError as e:
...
原因: except* 用于处理来自 asyncio 和并发任务的 ExceptionGroup。捕获 TaskGroup 或类似结构抛出的异常时应使用它。
12. 使用 tomllib 解析 TOML(Python 3.11+ 标准库)
错误:
import toml
data = toml.load("pyproject.toml")
正确:
import tomllib
with open("pyproject.toml", "rb") as f:
data = tomllib.load(f)
原因: tomllib 是内置模块,无需额外依赖。读取时使用二进制模式。
13. 使用 typing.override 装饰器(Python 3.12+)
错误:
class Child(Parent):
def method(self) -> str:
return "child"
正确:
from typing import override
class Child(Parent):
@override
def method(self) -> str:
return "child"
原因: @override 能明确表达意图,并发现方法名中的拼写错误。
14. 使用带守卫条件的结构化模式匹配
错误:
match x:
case (a, b):
if a > b:
...
正确:
match x:
case (a, b) if a > b:
...
原因: 守卫条件(if)可将逻辑保留在模式中,并避免嵌套条件判断。
15. 对抽象类型优先使用 collections.abc,而不是 typing
错误:
from typing import Iterable, Mapping
正确:
from collections.abc import Iterable, Mapping
原因: collections.abc 是 ABC 的规范来源。typing 虽会重新导出这些类型,但运行时检查优先使用 collections.abc。
模式
带类型参数的泛型类
class Bag[T]:
def __iter__(self) -> Iterator[T]:
...
def add(self, arg: T) -> None:
...
泛型类型别名
type ListOrSet[T] = list[T] | set[T]
使用 before 模式的 Pydantic model_validator
@model_validator(mode="before")
@classmethod
def check_card_number_not_present(cls, data: Any) -> Any:
if isinstance(data, dict) and "card_number" in data:
raise ValueError("'card_number' should not be included")
return data
匹配多个字面值
case 401 | 403 | 404:
return "Not allowed"
反模式
- 对于内置泛型,切勿使用
from typing import List, Dict, Union, Optional;应使用list、dict、X | Y、X | None。 - 使用 Pydantic 时,切勿使用
@validator、@root_validator或class Config;应使用 v2 API。 - 新代码中切勿使用
os.path;应使用pathlib.Path。 - 切勿使用
setup.py或setup.cfg;应使用pyproject.toml。 - 有 f-string 可用时,切勿使用
%或.format()。 - 切勿使用
toml包;应使用标准库tomllib(Python 3.11+)。 - 适用 Python 3.12+ 类型参数语法时,简单泛型切勿使用
TypeVar。 - 有 TaskGroup 可用时,结构化并发切勿使用
asyncio.gather(3.11+)。 - 适合使用 dataclass 或 Pydantic 模型时,切勿使用普通 dict 表示带类型的结构化数据。
- 切勿使用
from typing import Iterable, Mapping;应使用from collections.abc import ...。