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