Python 编程教程 / 25 - 最佳实践
第 25 章:最佳实践
汇总 Python 开发中的代码规范、设计模式、常见陷阱和项目组织经验。
25.1 代码规范
25.1.1 PEP 8 核心规则
| 规则 | 说明 | 示例 |
|---|---|---|
| 缩进 | 4 个空格 | 不使用 Tab |
| 行长度 | 79(严格)/ 88(Black) | 使用换行或括号 |
| 命名-变量 | snake_case | user_name |
| 命名-类 | PascalCase | UserProfile |
| 命名-常量 | UPPER_SNAKE_CASE | MAX_RETRY |
| 命名-私有 | 前导下划线 | _internal |
| 空行 | 顶层函数/类之间 2 行 | 类内方法之间 1 行 |
| 导入顺序 | 标准库 → 第三方 → 本地 | 使用 isort/ruff |
25.1.2 清洁代码原则
# ✅ 使用有意义的变量名
user_age = 25
max_retry_count = 3
# ❌ 不清晰的命名
a = 25
x = 3
# ✅ 函数只做一件事
def calculate_total(items: list[dict]) -> float:
return sum(item["price"] * item["quantity"] for item in items)
# ❌ 函数做了太多事
def process_order(order):
# 验证 + 计算 + 发邮件 + 记录日志 + ... 全部塞在一起
...
# ✅ 使用 Early Return 减少嵌套
def validate_user(user: dict) -> bool:
if not user.get("name"):
return False
if not user.get("email"):
return False
if user["age"] < 0:
return False
return True
# ❌ 深层嵌套
def validate_user(user: dict) -> bool:
if user.get("name"):
if user.get("email"):
if user["age"] >= 0:
return True
return False
25.2 Pythonic 惯用法
25.2.1 常用 Pythonic 写法
# 1. 解包赋值
a, b = b, a
first, *rest = [1, 2, 3, 4]
# 2. 列表推导
squares = [x**2 for x in range(10)]
# 3. 字典推导
inverted = {v: k for k, v in original.items()}
# 4. 生成器表达式
total = sum(x**2 for x in range(1_000_000))
# 5. walrus 运算符(Python 3.8+)
if (n := len(data)) > 10:
print(f"数据量 {n} 过大")
# 6. 上下文管理器
with open("file.txt") as f:
content = f.read()
# 7. enumerate 替代手动索引
for i, item in enumerate(items):
print(f"{i}: {item}")
# 8. zip 并行遍历
for name, score in zip(names, scores):
print(f"{name}: {score}")
# 9. 使用 get 安全取值
value = my_dict.get("key", "default")
# 10. 使用 setdefault
my_dict.setdefault("key", []).append(value)
# 11. 链式比较
if 0 < x < 100:
pass
# 12. any/all
if any(word in text for word in keywords):
pass
if all(x > 0 for x in numbers):
pass
25.2.2 避免的写法
# ❌ 用 == 比较 None
if x == None: ...
# ✅ 用 is
if x is None: ...
# ❌ 用 type() 检查类型
if type(x) is list: ...
# ✅ 用 isinstance()
if isinstance(x, list): ...
# ❌ 用 len() 检查空容器
if len(my_list) == 0: ...
# ✅ 直接布尔判断
if not my_list: ...
# ❌ 用 try/except 替代条件判断
try:
value = my_dict["key"]
except KeyError:
value = default
# ✅ 使用 get()
value = my_dict.get("key", default)
# ❌ 手动实现内置功能
total = 0
for x in items:
total += x
# ✅ 使用内置函数
total = sum(items)
25.3 常见陷阱
25.3.1 可变默认参数
# ❌ 经典陷阱
def append_to(item, target=[]):
target.append(item)
return target
append_to(1) # [1]
append_to(2) # [1, 2] ← 共享同一个列表!
# ✅ 正确做法
def append_to(item, target=None):
if target is None:
target = []
target.append(item)
return target
25.3.2 闭包中的变量绑定
# ❌ 陷阱
funcs = [lambda: i for i in range(5)]
print([f() for f in funcs]) # [4, 4, 4, 4, 4]
# ✅ 使用默认参数捕获
funcs = [lambda i=i: i for i in range(5)]
print([f() for f in funcs]) # [0, 1, 2, 3, 4]
25.3.3 浮点数精度
# ❌ 浮点数比较
0.1 + 0.2 == 0.3 # False
# ✅ 使用 math.isclose
import math
math.isclose(0.1 + 0.2, 0.3) # True
# ✅ 金融计算用 Decimal
from decimal import Decimal
Decimal("0.1") + Decimal("0.2") == Decimal("0.3") # True
25.3.4 作用域陷阱
x = 10
def foo():
print(x) # ❌ UnboundLocalError
x = 20 # 局部赋值导致 x 在整个函数中被当作局部变量
# ✅ 使用 global 或 nonlocal
def foo():
global x
print(x) # 10
x = 20
25.3.5 类型转换陷阱
# ❌ 链式比较可能出错
x = 5
if 1 < x < 10: # ✅ 正确
pass
# ❌ 字符串 join 时注意类型
numbers = [1, 2, 3]
# ",".join(numbers) # TypeError
# ✅ 需要先转换
",".join(str(n) for n in numbers) # "1,2,3"
25.4 设计模式
25.4.1 单例模式
from functools import cache
@cache
def get_config():
return load_config()
# 或使用类
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
25.4.2 工厂模式
from typing import Protocol
class Serializer(Protocol):
def serialize(self, data: dict) -> str: ...
class JSONSerializer:
def serialize(self, data: dict) -> str:
import json
return json.dumps(data)
class YAMLSerializer:
def serialize(self, data: dict) -> str:
import yaml
return yaml.dump(data)
def get_serializer(format: str) -> Serializer:
serializers = {"json": JSONSerializer, "yaml": YAMLSerializer}
cls = serializers.get(format)
if cls is None:
raise ValueError(f"Unknown format: {format}")
return cls()
25.4.3 策略模式
from typing import Callable
def process_data(data: list, strategy: Callable) -> list:
return [strategy(item) for item in data]
# 不同策略
uppercase = str.upper
lowercase = str.lower
strip = str.strip
words = [" hello ", " WORLD "]
print(process_data(words, uppercase))
print(process_data(words, strip))
25.4.4 依赖注入
from dataclasses import dataclass, field
@dataclass
class EmailService:
smtp_host: str = "localhost"
def send(self, to: str, subject: str, body: str) -> None:
print(f"Sending email to {to}")
@dataclass
class UserService:
email_service: EmailService = field(default_factory=EmailService)
def register(self, email: str) -> None:
# 业务逻辑
self.email_service.send(email, "Welcome", "Welcome aboard!")
# 测试时注入 Mock
class MockEmailService:
def send(self, to, subject, body):
pass
service = UserService(email_service=MockEmailService())
25.5 项目组织
25.5.1 推荐目录结构
myproject/
├── .github/
│ └── workflows/
│ └── ci.yml
├── src/
│ └── myproject/
│ ├── __init__.py
│ ├── main.py
│ ├── config.py
│ ├── models/
│ │ ├── __init__.py
│ │ └── user.py
│ ├── services/
│ │ ├── __init__.py
│ │ └── user_service.py
│ └── api/
│ ├── __init__.py
│ └── routes.py
├── tests/
│ ├── conftest.py
│ └── test_user_service.py
├── docs/
├── .env.example
├── .gitignore
├── pyproject.toml
├── README.md
└── Makefile
25.5.2 模块划分原则
分层架构:
API 层 → Service 层 → Repository 层 → Database
api/routes.py # 路由、请求验证、响应格式
services/ # 业务逻辑
models/ # 数据模型
repositories/ # 数据访问
config.py # 配置
25.6 类型安全
25.6.1 类型注解最佳实践
from typing import TypeVar, Generic, Protocol
from collections.abc import Sequence
# 函数签名
def process(items: Sequence[int]) -> list[str]:
return [str(x) for x in items]
# 泛型
T = TypeVar("T")
def first(items: Sequence[T]) -> T | None:
return items[0] if items else None
# Protocol(结构化类型)
class Renderable(Protocol):
def render(self) -> str: ...
def render_all(items: Sequence[Renderable]) -> str:
return "\n".join(item.render() for item in items)
# TypedDict
from typing import TypedDict
class UserData(TypedDict):
name: str
age: int
email: str | None
25.6.2 运行时类型检查
from pydantic import BaseModel, Field, field_validator
class CreateUser(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
email: str
age: int = Field(..., ge=0, le=150)
@field_validator("email")
@classmethod
def validate_email(cls, v: str) -> str:
if "@" not in v:
raise ValueError("Invalid email")
return v.lower()
# 自动验证
user = CreateUser(name="Alice", email="alice@example.com", age=30)
25.7 文档与注释
25.7.1 Docstring 规范
def calculate_shipping(
weight: float,
distance: float,
express: bool = False,
) -> float:
"""计算运费。
根据重量和距离计算运费,支持普通和快递两种方式。
Args:
weight: 包裹重量,单位为千克,必须大于 0。
distance: 运输距离,单位为公里。
express: 是否使用快递,默认为 False。
Returns:
运费金额(元)。
Raises:
ValueError: 当重量或距离为非正数时。
Examples:
>>> calculate_shipping(1.0, 100)
10.0
>>> calculate_shipping(1.0, 100, express=True)
20.0
"""
if weight <= 0 or distance <= 0:
raise ValueError("重量和距离必须为正数")
rate = 0.1 if not express else 0.2
return weight * distance * rate
25.8 常用工具链
| 用途 | 工具 | 说明 |
|---|---|---|
| 格式化 | Ruff | 一体化 lint + format |
| 类型检查 | Mypy / Pyright | 静态类型分析 |
| 测试 | pytest | 测试框架 |
| 文档 | MkDocs / Sphinx | 文档生成 |
| 包管理 | uv / Poetry | 依赖管理 |
| 容器 | Docker | 应用容器化 |
| CI/CD | GitHub Actions | 自动化流水线 |
| 安全 | bandit, pip-audit | 安全扫描 |
25.9 Python 之禅回顾
import this
开发中最重要的原则:
- 可读性很重要 — 代码是写给人看的
- 显式优于隐式 — 不要隐藏意图
- 简单优于复杂 — 选择最简单的方案
- 错误不应被静默忽略 — 明确处理异常
- 应该有一种明显的方式 — 遵循惯例
25.10 注意事项
🔴 注意:
- 不要过早优化,先让它工作,再让它正确,最后让它快
- 不要重复造轮子,优先使用标准库和成熟的第三方库
- 不要忽略类型检查,Mypy 能在运行前发现很多错误
- 不要跳过测试,自动化测试是代码质量的保障
💡 提示:
- 遵循 PEP 8,使用 Ruff 自动格式化
- 所有公开接口都写类型注解和文档字符串
- 每个 PR 都要经过 CI 检查
- 定期更新依赖,扫描安全漏洞
📌 业务场景:
Python 项目启动清单:
□ 选择 Python 版本(3.11+)
□ 创建 pyproject.toml
□ 配置虚拟环境(uv venv)
□ 设置项目结构(src layout)
□ 配置 Ruff + Mypy
□ 编写 .gitignore
□ 设置 GitHub Actions CI
□ 编写 README.md
□ 添加 LICENSE
□ 配置 pre-commit
25.11 扩展阅读
- PEP 8 — 代码风格指南
- PEP 20 — The Zen of Python
- PEP 484 — Type Hints
- Google Python Style Guide
- The Hitchhiker’s Guide to Python
- Real Python — Python 教程网站
- 《流畅的 Python》(Fluent Python) — Luciano Ramalho
- 《Python Cookbook》— David Beazley
- Python Anti-Patterns
🎉 恭喜完成全部 25 章 Python 编程教程!
持续学习,持续实践。Python 的精髓在于简洁与优雅,用代码表达思想,用测试保障质量,用文档传递知识。