python-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

When to use

适用场景

Use this skill when writing or reviewing Python code targeting Python 3.12+. It enforces modern type hints, async patterns, Pydantic v2 API, project structure, and standard library usage. Agents trained on older codebases often emit outdated patterns; this skill corrects that.
当你编写或审查针对Python 3.12+的Python代码时,可以使用本技能。它会强制采用现代类型提示、异步模式、Pydantic v2 API、项目结构以及标准库用法。基于旧代码库训练的Agent通常会输出过时的模式,本技能可以纠正这一问题。

Critical Rules

核心规则

1. Use modern type hint syntax

1. 使用现代类型提示语法

Wrong:
python
from typing import Union, List, Dict, Optional

def process(items: List[str]) -> Optional[Dict[str, Union[int, str]]]:
    ...
Correct:
python
def process(items: list[str]) -> dict[str, int | str] | None:
    ...
Why:
list[str]
and
X | Y
are built-in in Python 3.9+ (PEP 585, PEP 604). Importing
typing.List
,
typing.Union
,
typing.Optional
is verbose and deprecated for built-in generics.
错误示例:
python
from typing import Union, List, Dict, Optional

def process(items: List[str]) -> Optional[Dict[str, Union[int, str]]]:
    ...
正确示例:
python
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. Use type parameter syntax (PEP 695, Python 3.12+)

2. 使用类型参数语法(PEP 695,Python 3.12+)

Wrong:
python
from typing import TypeVar

T = TypeVar("T")

def max(args: list[T]) -> T:
    ...
Correct:
python
def max[T](args: list[T]) -> T:
    ...
Why: PEP 695 type parameters are simpler and local to the function/class. Same for generic classes:
class Bag[T]:
instead of
class Bag(Generic[T]):
.
错误示例:
python
from typing import TypeVar

T = TypeVar("T")

def max(args: list[T]) -> T:
    ...
正确示例:
python
def max[T](args: list[T]) -> T:
    ...
原因: PEP 695定义的类型参数语法更简洁,且作用域局限于函数/类。泛型类同理:应使用
class Bag[T]:
而非
class Bag(Generic[T]):

3. Use match/case instead of if/elif chains for pattern matching

3. 使用match/case替代if/elif链进行模式匹配

Wrong:
python
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"
Correct:
python
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"
Why: Structural pattern matching (PEP 634) is clearer for disjoint cases and supports destructuring. Use
case 401 | 403 | 404:
for multiple literals.
错误示例:
python
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"
正确示例:
python
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. Use Pydantic v2 API, never v1

4. 使用Pydantic v2 API,禁止使用v1版本

Wrong:
python
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):
        ...
Correct:
python
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
Why: Pydantic v1 decorators and
class Config
are deprecated. v2 uses
model_config
,
field_validator
,
model_validator
with explicit modes.
错误示例:
python
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):
        ...
正确示例:
python
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. Use uv for package management in new projects

5. 新项目使用uv进行包管理

Wrong:
bash
pip install -r requirements.txt
poetry init
Correct:
bash
uv init
uv add requests pydantic
uv sync
Why: uv is fast, has a modern lockfile, and supports pyproject.toml natively. Prefer it for new projects.
错误示例:
bash
pip install -r requirements.txt
poetry init
正确示例:
bash
uv init
uv add requests pydantic
uv sync
原因: uv速度快,拥有现代锁文件,原生支持pyproject.toml。新项目优先选择它。

6. Use pyproject.toml for project configuration

6. 使用pyproject.toml进行项目配置

Wrong:
python
undefined
错误示例:
python
undefined

setup.py

setup.py

from setuptools import setup setup(name="myapp", version="0.1", ...)

**Correct:**

```toml
from setuptools import setup setup(name="myapp", version="0.1", ...)

**正确示例:**

```toml

pyproject.toml

pyproject.toml

[project] name = "myapp" version = "0.1" dependencies = ["requests"]

**Why:** setup.py and setup.cfg are legacy. pyproject.toml (PEP 517/518) is the standard.
[project] name = "myapp" version = "0.1" dependencies = ["requests"]

**原因:** setup.py和setup.cfg属于遗留方案。pyproject.toml(符合PEP 517/518规范)是当前的标准配置文件。

7. Use pathlib.Path instead of os.path

7. 使用pathlib.Path替代os.path

Wrong:
python
import os
path = os.path.join(os.getcwd(), "data", "file.txt")
if os.path.exists(path):
    with open(path) as f:
        ...
Correct:
python
from pathlib import Path
path = Path.cwd() / "data" / "file.txt"
if path.exists():
    path.read_text()
Why: pathlib is object-oriented, clearer, and cross-platform. Prefer
Path.read_text()
/
write_text()
over
open()
for simple reads/writes.
错误示例:
python
import os
path = os.path.join(os.getcwd(), "data", "file.txt")
if os.path.exists(path):
    with open(path) as f:
        ...
正确示例:
python
from pathlib import Path
path = Path.cwd() / "data" / "file.txt"
if path.exists():
    path.read_text()
原因: pathlib采用面向对象设计,更清晰且跨平台。对于简单的读写操作,优先使用
Path.read_text()
/
write_text()
而非
open()

8. Use f-strings instead of .format() or % formatting

8. 使用f-string替代.format()或%格式化

Wrong:
python
"User %s has %d items" % (name, count)
"User {} has {} items".format(name, count)
Correct:
python
f"User {name} has {count} items"
Why: f-strings are faster and more readable.
错误示例:
python
"User %s has %d items" % (name, count)
"User {} has {} items".format(name, count)
正确示例:
python
f"User {name} has {count} items"
原因: f-string速度更快,可读性更强。

9. Use dataclasses or Pydantic for structured data, not plain dicts

9. 使用dataclasses或Pydantic处理结构化数据,避免使用普通字典

Wrong:
python
def get_user() -> dict:
    return {"name": "Alice", "age": 30}
Correct:
python
from dataclasses import dataclass

@dataclass
class User:
    name: str
    age: int

def get_user() -> User:
    return User(name="Alice", age=30)
Why: Structured types give type safety and IDE support. Use Pydantic when you need validation.
错误示例:
python
def get_user() -> dict:
    return {"name": "Alice", "age": 30}
正确示例:
python
from dataclasses import dataclass

@dataclass
class User:
    name: str
    age: int

def get_user() -> User:
    return User(name="Alice", age=30)
原因: 结构化类型提供类型安全和IDE支持。当需要验证功能时,使用Pydantic。

10. Use asyncio.TaskGroup instead of asyncio.gather (Python 3.11+)

10. 使用asyncio.TaskGroup替代asyncio.gather(Python 3.11+)

Wrong:
python
import asyncio
results = await asyncio.gather(f1(), f2(), f3())
Correct:
python
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())
Why: TaskGroup propagates exceptions correctly and cancels other tasks on failure.
错误示例:
python
import asyncio
results = await asyncio.gather(f1(), f2(), f3())
正确示例:
python
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. Use exception groups and except* (Python 3.11+)

11. 使用异常组和except*(Python 3.11+)

Wrong:
python
try:
    ...
except (ValueError, TypeError) as e:
    ...
Correct (when dealing with ExceptionGroup from concurrency):
python
try:
    ...
except* TypeError as e:
    print(f"caught {type(e)} with nested {e.exceptions}")
except* OSError as e:
    ...
Why:
except*
handles ExceptionGroups from asyncio and concurrent tasks. Use when catching from TaskGroup or similar.
错误示例:
python
try:
    ...
except (ValueError, TypeError) as e:
    ...
正确示例(处理并发场景中的ExceptionGroup时):
python
try:
    ...
except* TypeError as e:
    print(f"caught {type(e)} with nested {e.exceptions}")
except* OSError as e:
    ...
原因:
except*
用于处理来自asyncio和并发任务的ExceptionGroup。在捕获TaskGroup或类似组件抛出的异常时使用。

12. Use tomllib for TOML parsing (Python 3.11+ stdlib)

12. 使用tomllib解析TOML(Python 3.11+标准库)

Wrong:
python
import toml
data = toml.load("pyproject.toml")
Correct:
python
import tomllib
with open("pyproject.toml", "rb") as f:
    data = tomllib.load(f)
Why: tomllib is built-in; no extra dependency. Read in binary mode.
错误示例:
python
import toml
data = toml.load("pyproject.toml")
正确示例:
python
import tomllib
with open("pyproject.toml", "rb") as f:
    data = tomllib.load(f)
原因: tomllib是内置库,无需额外依赖。需以二进制模式读取文件。

13. Use typing.override decorator (Python 3.12+)

13. 使用typing.override装饰器(Python 3.12+)

Wrong:
python
class Child(Parent):
    def method(self) -> str:
        return "child"
Correct:
python
from typing import override

class Child(Parent):
    @override
    def method(self) -> str:
        return "child"
Why: @override makes intent explicit and catches typos in method names.
错误示例:
python
class Child(Parent):
    def method(self) -> str:
        return "child"
正确示例:
python
from typing import override

class Child(Parent):
    @override
    def method(self) -> str:
        return "child"
原因: @override可以明确表达重写父类方法的意图,并能捕获方法名拼写错误。

14. Use structural pattern matching with guards

14. 使用带守卫条件的结构化模式匹配

Wrong:
python
match x:
    case (a, b):
        if a > b:
            ...
Correct:
python
match x:
    case (a, b) if a > b:
        ...
Why: Guards (
if
) keep logic in the pattern and avoid nested conditionals.
错误示例:
python
match x:
    case (a, b):
        if a > b:
            ...
正确示例:
python
match x:
    case (a, b) if a > b:
        ...
原因: 守卫条件(
if
)将逻辑整合到模式中,避免嵌套条件判断。

15. Prefer collections.abc over typing for abstract types

15. 优先使用collections.abc而非typing中的抽象类型

Wrong:
python
from typing import Iterable, Mapping
Correct:
python
from collections.abc import Iterable, Mapping
Why: collections.abc is the canonical source for ABCs. typing re-exports them but collections.abc is preferred for runtime checks.
错误示例:
python
from typing import Iterable, Mapping
正确示例:
python
from collections.abc import Iterable, Mapping
原因: collections.abc是抽象基类(ABC)的标准来源。typing模块虽然也导出这些类,但在运行时检查时,优先使用collections.abc。

Patterns

推荐模式

Generic class with type parameter

带类型参数的泛型类

python
class Bag[T]:
    def __iter__(self) -> Iterator[T]:
        ...
    def add(self, arg: T) -> None:
        ...
python
class Bag[T]:
    def __iter__(self) -> Iterator[T]:
        ...
    def add(self, arg: T) -> None:
        ...

Generic type alias

泛型类型别名

python
type ListOrSet[T] = list[T] | set[T]
python
type ListOrSet[T] = list[T] | set[T]

Pydantic model_validator before mode

Pydantic的model_validator前置模式

python
@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
python
@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

Match with multiple literals

匹配多个字面量

python
case 401 | 403 | 404:
    return "Not allowed"
python
case 401 | 403 | 404:
    return "Not allowed"

Anti-Patterns

反模式

  • Never use
    from typing import List, Dict, Union, Optional
    for built-in generics; use
    list
    ,
    dict
    ,
    X | Y
    ,
    X | None
    .
  • Never use
    @validator
    or
    @root_validator
    or
    class Config
    with Pydantic; use v2 API.
  • Never use
    os.path
    for new code; use
    pathlib.Path
    .
  • Never use
    setup.py
    or
    setup.cfg
    ; use
    pyproject.toml
    .
  • Never use
    %
    or
    .format()
    when f-strings are available.
  • Never use
    toml
    package; use stdlib
    tomllib
    (Python 3.11+).
  • Never use
    TypeVar
    for simple generics when Python 3.12+ type parameter syntax applies.
  • Never use
    asyncio.gather
    for structured concurrency when TaskGroup is available (3.11+).
  • Never use plain dicts for typed structured data when dataclasses or Pydantic models fit.
  • Never use
    from typing import Iterable, Mapping
    ; use
    from collections.abc import ...
    .
  • 永远不要对内置泛型类型使用
    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可用时(Python 3.11+),永远不要使用
    asyncio.gather
    进行结构化并发。
  • 当dataclasses或Pydantic模型适用时,永远不要使用普通字典处理带类型的结构化数据。
  • 永远不要使用
    from typing import Iterable, Mapping
    ;应使用
    from collections.abc import ...