跳转到内容

Python开发规范

Python 通用编码规范(PEP 8、类型注解、虚拟环境)


  • [Requirement] 缩进使用 4 个空格,禁止使用 Tab(除非与现有代码保持一致)。
  • [Requirement] 行宽上限 120 字符,超出必须换行。换行优先使用括号内隐式续行,避免使用反斜杠 \。
  • [Standard] 空行规则:顶层函数/类之间空 2 行;类内方法之间空 1 行;函数内逻辑分组之间可空 1 行;文件末尾保留 1 个空行。
  • [Standard] import 顺序分为三组,组间空 1 行:① 标准库 ② 第三方库 ③ 本地项目;同组内按字母序排列。
  • [Standard] 命名约定:
    • 模块/包名:全小写,单词间下划线(user_service)。
    • 类名:大驼峰(UserService),异常类以 Error 结尾。
    • 函数/方法/变量:小写下划线(get_user_by_id)。
    • 常量:全大写下划线(MAX_RETRY_COUNT = 3)。
    • 内部成员:单下划线前缀(_internal_state);私有属性双下划线(__secret)仅在需要名称改写时使用。
  • [Standard] 字符串统一使用双引号 "...",与 JSON 习惯保持一致,避免单双引号混用。
  • [Suggestion] 文件编码声明省略(Python 3 默认 UTF-8),不要写 # -*- coding: utf-8 -*-。
  • [Caution] 禁止使用 from module import *,会污染命名空间且遮蔽符号。
  • [Caution] 禁止在 except 中使用裸 except:,必须捕获具体异常,至少 except Exception:。
# 正确:import 分组
import os
import sys
from pathlib import Path
 
import requests
from pydantic import BaseModel
 
from myapp.models import User
from myapp.utils import logger
# 正确:长行隐式换行
result = some_function(
    arg1,
    arg2,
    arg3,
)
# 正确:命名约定
MAX_PAGE_SIZE = 100
 
class UserService:
    def __init__(self, repo: UserRepository) -> None:
        self._repo = repo
 
    def get_active_users(self, page: int) -> list[User]:
        ...
# 反模式:裸 except 吞掉所有异常,掩盖 Bug
try:
    do_something()
except:
    pass
 
# 反模式:通配符导入
from utils import *
 
# 反模式:反斜杠续行
result = foo(arg1, arg2) \
    + bar(arg3, arg4)

  • [Requirement] 所有公开函数/方法必须标注参数类型和返回值类型;私有方法至少标注返回类型。
  • [Requirement] Python 3.9+ 直接使用内置泛型(list[int]、dict[str, Any]),禁止使用 typing.List 等旧写法。
  • [Standard] 使用 Optional[X] 或 X | None 表示可空,项目内统一一种写法(推荐 X | None,Python 3.10+)。
  • [Standard] 类属性必须标注类型,__init__ 的赋值属性优先用 dataclass 或 pydantic.BaseModel 自动管理。
  • [Default] 启用 mypy --strict 或至少 mypy 基础检查,CI 中作为门禁。
  • [Suggestion] 第三方库缺失类型存根时,在 mypy.ini 中配置 ignore_missing_imports,不要在调用处到处 # type: ignore。
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any
 
@dataclass
class User:
    id: int
    name: str
    email: str | None = None
 
def find_user(user_id: int, users: list[User]) -> User | None:
    for u in users:
        if u.id == user_id:
            return u
    return None
 
def retry(fn: Callable[[], Any], times: int = 3) -> Any:
    ...
# 反模式:无类型注解
def get_user(user_id, users):
    ...
 
# 反模式:Python 3.9+ 仍使用旧泛型
from typing import List, Dict
 
def process(items: List[int]) -> Dict[str, int]:
    ...

  • [Requirement] 禁止在系统全局 Python 环境安装依赖,必须使用隔离的虚拟环境。
  • [Standard] 新项目统一使用 uv 管理环境与依赖;存量项目若使用 venv + pip,保持一致,不混用。
  • [Standard] 虚拟环境目录(.venv)必须加入 .gitignore,禁止提交。
  • [Standard] 锁定 Python 版本:通过 pyproject.toml 的 requires-python 或 .python-version 文件指定,团队内统一。
  • [Suggestion] 使用 uv venv .venv --python 3.12 创建环境,用 uv sync 安装锁定依赖,保证可复现。
# 创建虚拟环境(uv)
uv venv .venv --python 3.12
 
# 激活(zsh/bash)
source .venv/bin/activate
 
# 安装锁定依赖
uv sync
# .gitignore
.venv/
__pycache__/
*.pyc
# 反模式:直接用系统 pip 安装
pip install requests
 
# 反模式:提交虚拟环境目录到 Git
git add .venv/

  • [Requirement] 依赖必须锁定版本并提交锁定文件(uv.lock、poetry.lock 或 requirements.txt 带版本号),禁止 requirements.txt 中出现无版本约束的包名。
  • [Requirement] 区分运行时依赖与开发依赖:dependencies 放运行必需,[tool.uv.dev-dependencies] 或 requirements-dev.txt 放测试/格式化/Lint 工具。
  • [Standard] 优先选择维护活跃、下载量大、有类型存根的库;引入新依赖需在 PR 中说明理由。
  • [Standard] 使用 pyproject.toml 作为单一事实源,禁止同时维护 setup.py、setup.cfg、requirements.txt 多套配置。
  • [Caution] 禁止使用 git+https:// 直接依赖未发布的仓库分支,必须使用正式 release 或私有包源。
  • [Caution] 定期运行 uv pip list --outdated 或 pip-audit 检查依赖安全漏洞,高危漏洞须在 72 小时内修复。
# pyproject.toml
[project]
name = "myapp"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "fastapi>=0.115,<0.116",
    "uvicorn[standard]>=0.30",
]
 
[tool.uv]
dev-dependencies = [
    "pytest>=8.0",
    "ruff>=0.6",
    "mypy>=1.11",
]
# 添加运行时依赖
uv add requests
 
# 添加开发依赖
uv add --dev pytest-asyncio
 
# 导出锁定(兼容旧 CI)
uv export --format requirements-txt > requirements.txt
# 反模式:无版本锁定,不可复现
requests
pytest
flask
# 反模式:直接依赖 git 分支
uv add "git+https://github.com/x/y.git@main"

  • [Requirement] 核心业务逻辑必须有单元测试覆盖,行覆盖率不低于 80%,关键模块不低于 90%。
  • [Requirement] 测试文件以 test_ 开头,测试函数以 test_ 开头,与被测源码目录镜像对应(src/a/b.py → tests/a/test_b.py)。
  • [Standard] 测试框架统一使用 pytest,禁止混用 unittest。
  • [Standard] 断言使用 pytest 原生断言(assert x == y),不要用 assertEqual 等 unittest 风格。
  • [Standard] 使用 fixture 管理测试数据与依赖,每个 fixture 单一职责;数据库/网络相关 fixture 标注 scope。
  • [Default] 测试应独立、可重复、无副作用:禁止测试之间共享可变状态,禁止依赖执行顺序。
  • [Suggestion] 使用 parametrize 覆盖边界值与异常分支,减少重复样板代码。
  • [Caution] 禁止在测试中调用真实外部服务(数据库/HTTP/缓存),必须用 mock 或测试替身;确需集成测试时独立标记并分离执行。
# tests/test_user_service.py
import pytest
 
from myapp.services import UserService
from myapp.repositories import UserRepository
 
 
@pytest.fixture
def repo() -> UserRepository:
    return UserRepository()
 
 
@pytest.fixture
def service(repo: UserRepository) -> UserService:
    return UserService(repo)
 
 
@pytest.mark.parametrize(
    "user_id, expected",
    [
        (1, "Alice"),
        (999, None),
    ],
)
def test_get_user(service: UserService, user_id: int, expected: str | None) -> None:
    result = service.get_user(user_id)
    assert result.name == expected if result else result is None
 
 
def test_get_user_raises_on_invalid_id(service: UserService) -> None:
    with pytest.raises(ValueError, match="user_id must be positive"):
        service.get_user(-1)
# pytest.ini / pyproject.toml
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
markers = [
    "slow: marks tests as slow (deselect with '-m \"not slow\"')",
    "integration: integration tests requiring external services",
]
# 反模式:测试之间共享状态
_counter = 0
 
def test_first():
    global _counter
    _counter += 1
 
def test_second():
    assert _counter == 1  # 依赖执行顺序
 
# 反模式:调用真实网络
def test_api_call():
    resp = requests.get("https://api.example.com/users")
    assert resp.status_code == 200