名称: python-best-practices 描述: | 提供专家指导,用于编写遵循行业最佳实践的专业Python代码,包括PEP 8合规性、测试、类型提示、错误处理和现代工具链。
在编写新Python代码、重构现有代码、设置Python项目、实施测试或确保代码质量和可维护性时使用此技能。
强调:PEP 8、模块化、DRY原则、测试驱动开发(TDD)、虚拟环境(uv)和现代工具(Ruff、Black、Mypy)。 允许工具: [“Read”, “Edit”, “Write”, “Bash”, “Grep”, “Glob”]
Python最佳实践技能
此技能提供专家指导,用于编写专业、可维护的Python代码,遵循行业最佳实践和标准。
何时使用此技能
在以下情况使用此技能:
- 编写新的Python函数、类或模块
- 重构现有Python代码以提高质量
- 设置具有适当结构的新Python项目
- 实施单元测试或采用TDD
- 添加类型提示以提高代码清晰度
- 配置代码检查、格式化和类型检查工具
- 管理依赖项和虚拟环境
- 提高代码可读性和可维护性
- 遵循PEP 8样式指南
核心原则
1. PEP 8: Python代码样式指南
关键指南:
- 缩进:每个缩进级别使用4个空格(切勿使用制表符)
- 行长度:限制行长为79个字符(代码为99,文档字符串/注释为72)
- 空行:顶层函数/类之间2个空行,类内1个
- 导入:
- 每行一个导入
- 顺序:标准库、第三方、本地(每组用空行分隔)
- 避免通配符导入(
from module import *)
- 命名约定:
snake_case用于函数、变量、方法PascalCase用于类UPPER_CASE用于常量- 前导下划线
_private用于内部使用
- 空格:
- 无尾随空格
- 运算符周围一个空格:
x = 1,而非x=1 - 函数括号前无空格:
func(x),而非func (x)
示例:
"""模块文档字符串描述目的。"""
import os
import sys
from pathlib import Path
import numpy as np
import pandas as pd
from mypackage.module import MyClass
# 常量
MAX_RETRIES = 3
DEFAULT_TIMEOUT = 30
class DataProcessor:
"""处理和数据分析集。
属性:
name: 处理器名称
threshold: 最小值阈值
"""
def __init__(self, name: str, threshold: float = 0.5):
"""初始化处理器。
参数:
name: 处理器名称
threshold: 过滤阈值(默认:0.5)
"""
self.name = name
self.threshold = threshold
def process_data(self, data: list[float]) -> list[float]:
"""通过过滤低于阈值的数据处理数据。
参数:
data: 要处理的数值列表
返回:
仅包含值 >= 阈值的过滤列表
抛出:
ValueError: 如果数据为空
"""
if not data:
raise ValueError("数据不能为空")
return [x for x in data if x >= self.threshold]
2. 可读性和清晰度
编写自文档化代码:
# 差:变量名不清晰
def calc(x, y, z):
return x * y / z
# 好:描述性名称
def calculate_unit_price(total_cost: float, quantity: int, tax_rate: float) -> float:
"""计算含税单价。"""
return total_cost * (1 + tax_rate) / quantity
使用文档字符串:
def fetch_user_data(user_id: int, include_history: bool = False) -> dict:
"""从数据库获取用户数据。
参数:
user_id: 用户的唯一标识符
include_history: 是否包含交易历史
返回:
包含用户信息的字典,键为:
- 'name': 用户全名
- 'email': 用户电子邮件地址
- 'history': 交易列表(如果include_history=True)
抛出:
UserNotFoundError: 如果user_id不存在
DatabaseError: 如果连接失败
示例:
>>> user = fetch_user_data(123, include_history=True)
>>> print(user['name'])
'John Doe'
"""
# 实现...
优先显式而非隐式:
# 差:隐式行为
def process(items):
return [x for x in items if x]
# 好:显式意图
def filter_non_empty_items(items: list) -> list:
"""从项目中移除None和空字符串值。"""
return [item for item in items if item is not None and item != ""]
3. 模块化和可重用性(DRY原则)
单一职责原则:
# 差:函数做太多事
def process_and_save_report(data):
# 处理数据
cleaned = [x.strip() for x in data]
filtered = [x for x in cleaned if len(x) > 0]
# 计算统计
total = sum(len(x) for x in filtered)
avg = total / len(filtered)
# 格式化报告
report = f"Total: {total}, Average: {avg}"
# 保存到文件
with open('report.txt', 'w') as f:
f.write(report)
return report
# 好:分离关注点
def clean_data(data: list[str]) -> list[str]:
"""移除空白和空字符串。"""
cleaned = [item.strip() for item in data]
return [item for item in cleaned if item]
def calculate_statistics(data: list[str]) -> dict:
"""计算字符串长度统计。"""
lengths = [len(item) for item in data]
return {
'total': sum(lengths),
'average': sum(lengths) / len(lengths) if lengths else 0,
'count': len(lengths)
}
def format_report(stats: dict) -> str:
"""将统计格式化为可读报告。"""
return f"Total: {stats['total']}, Average: {stats['average']:.2f}"
def save_report(content: str, filepath: Path) -> None:
"""将报告内容保存到文件。"""
filepath.write_text(content)
# 用法
cleaned = clean_data(data)
stats = calculate_statistics(cleaned)
report = format_report(stats)
save_report(report, Path('report.txt'))
避免重复:
# 差:重复逻辑
def calculate_circle_area(radius):
return 3.14159 * radius * radius
def calculate_circle_circumference(radius):
return 2 * 3.14159 * radius
# 好:可重用常量和函数
import math
def calculate_circle_area(radius: float) -> float:
"""计算圆面积。"""
return math.pi * radius ** 2
def calculate_circle_circumference(radius: float) -> float:
"""计算圆周长。"""
return 2 * math.pi * radius
def calculate_circle_properties(radius: float) -> dict:
"""计算所有圆属性。"""
return {
'area': calculate_circle_area(radius),
'circumference': calculate_circle_circumference(radius)
}
使用类处理相关功能:
class DataValidator:
"""根据定义规则验证数据。"""
def __init__(self, min_length: int = 0, max_length: int = 100):
self.min_length = min_length
self.max_length = max_length
def validate_length(self, value: str) -> bool:
"""检查字符串长度是否在边界内。"""
return self.min_length <= len(value) <= self.max_length
def validate_email(self, email: str) -> bool:
"""检查电子邮件格式是否有效。"""
return '@' in email and '.' in email.split('@')[1]
def validate_all(self, data: dict) -> dict[str, bool]:
"""验证数据字典中的所有字段。"""
return {
'email': self.validate_email(data.get('email', '')),
'name': self.validate_length(data.get('name', ''))
}
4. 测试和TDD
编写可测试代码:
# 差:难以测试(依赖于外部状态)
def get_config_value(key):
with open('/etc/myapp/config.ini') as f:
for line in f:
if line.startswith(key):
return line.split('=')[1].strip()
# 好:通过依赖注入可测试
def get_config_value(key: str, config_path: Path) -> str:
"""从文件获取配置值。"""
content = config_path.read_text()
for line in content.splitlines():
if line.startswith(key):
return line.split('=')[1].strip()
raise KeyError(f"配置键 '{key}' 未找到")
单元测试结构:
import pytest
from mymodule import calculate_unit_price, UserNotFoundError
class TestCalculateUnitPrice:
"""calculate_unit_price函数的测试套件。"""
def test_basic_calculation(self):
"""测试不含税的基本价格计算。"""
result = calculate_unit_price(100.0, 10, 0.0)
assert result == 10.0
def test_with_tax(self):
"""测试含税价格计算。"""
result = calculate_unit_price(100.0, 10, 0.2)
assert result == 12.0
def test_zero_quantity_raises_error(self):
"""测试零数量抛出ValueError。"""
with pytest.raises(ZeroDivisionError):
calculate_unit_price(100.0, 0, 0.1)
@pytest.mark.parametrize("total,qty,tax,expected", [
(100, 10, 0.0, 10.0),
(100, 10, 0.1, 11.0),
(50, 5, 0.2, 12.0),
])
def test_multiple_scenarios(self, total, qty, tax, expected):
"""测试多个计算场景。"""
assert calculate_unit_price(total, qty, tax) == pytest.approx(expected)
TDD方法:
# 步骤1:先写测试
def test_parse_csv_line():
"""测试CSV行解析。"""
result = parse_csv_line('John,Doe,30')
assert result == {'first': 'John', 'last': 'Doe', 'age': 30}
# 步骤2:实现最少代码以通过
def parse_csv_line(line: str) -> dict:
"""将CSV行解析为字典。"""
parts = line.split(',')
return {
'first': parts[0],
'last': parts[1],
'age': int(parts[2])
}
# 步骤3:重构同时保持测试通过
def parse_csv_line(line: str, headers: list[str] = None) -> dict:
"""将CSV行解析为字典,可选头。"""
if headers is None:
headers = ['first', 'last', 'age']
parts = line.split(',')
result = {}
for i, header in enumerate(headers):
value = parts[i].strip()
# 如果头为'age',转换为int
result[header] = int(value) if header == 'age' else value
return result
5. 错误处理
使用特定异常:
# 差:通用异常
def divide(a, b):
if b == 0:
raise Exception("不能除以零")
return a / b
# 好:特定异常
class DivisionByZeroError(ValueError):
"""当尝试除以零时抛出。"""
pass
def divide(a: float, b: float) -> float:
"""除两个数。
参数:
a: 分子
b: 分母
返回:
除法结果
抛出:
DivisionByZeroError: 如果分母为零
"""
if b == 0:
raise DivisionByZeroError(f"不能除 {a} 以零")
return a / b
适当异常处理:
# 差:裸异常
try:
result = risky_operation()
except:
print("发生错误")
# 好:特定异常与上下文
import logging
logger = logging.getLogger(__name__)
def process_file(filepath: Path) -> dict:
"""处理文件并返回解析数据。"""
try:
content = filepath.read_text()
return parse_content(content)
except FileNotFoundError:
logger.error(f"文件未找到:{filepath}")
raise
except PermissionError:
logger.error(f"权限被拒绝:{filepath}")
raise
except ValueError as e:
logger.error(f"{filepath} 中无效内容:{e}")
raise
except Exception as e:
logger.exception(f"处理 {filepath} 时意外错误")
raise
资源管理的上下文管理器:
# 好:自动清理
from pathlib import Path
from contextlib import contextmanager
@contextmanager
def open_database(db_path: Path):
"""数据库连接的上下文管理器。"""
conn = connect_to_database(db_path)
try:
yield conn
finally:
conn.close()
# 用法
with open_database(Path('data.db')) as db:
results = db.query('SELECT * FROM users')
6. 虚拟环境和依赖管理
使用uv(现代、快速包管理器):
# 使用uv创建新项目
uv venv
# 激活虚拟环境
source .venv/bin/activate # Linux/Mac
# 或
.venv\Scripts\activate # Windows
# 安装依赖
uv pip install pandas numpy pytest
# 安装特定版本
uv pip install "requests>=2.28.0,<3.0"
# 安装开发依赖
uv pip install --dev pytest black ruff mypy
# 创建需求文件
uv pip freeze > requirements.txt
# 从需求安装
uv pip install -r requirements.txt
项目结构with pyproject.toml:
[project]
name = "my-project"
version = "0.1.0"
description = "结构良好的Python项目"
authors = [{name = "Your Name", email = "you@example.com"}]
requires-python = ">=3.11"
dependencies = [
"pandas>=2.0.0",
"numpy>=1.24.0",
"requests>=2.28.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"black>=23.0.0",
"ruff>=0.1.0",
"mypy>=1.0.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.black]
line-length = 99
target-version = ['py311']
[tool.ruff]
line-length = 99
target-version = "py311"
select = ["E", "F", "I", "N", "W", "B", "C4"]
[tool.mypy]
python_version = "3.11"
strict = true
warn_return_any = true
warn_unused_configs = true
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = "test_*.py"
python_functions = "test_*"
7. 现代Python工具链
Ruff:快速代码检查和格式化器
# 安装
uv pip install ruff
# 检查代码
ruff check .
# 自动修复问题
ruff check --fix .
# 格式化代码
ruff format .
# 配置在pyproject.toml
[tool.ruff]
line-length = 99
select = [
"E", # pycodestyle错误
"F", # pyflakes
"I", # isort
"N", # pep8-naming
"W", # pycodestyle警告
"B", # flake8-bugbear
"C4", # flake8-comprehensions
]
ignore = ["E501"] # 行太长(由格式化器处理)
Black:代码格式化器
# 安装
uv pip install black
# 格式化文件
black myproject/
# 检查而不修改
black --check myproject/
# 配置
[tool.black]
line-length = 99
target-version = ['py311']
include = '\.pyi?$'
Mypy:静态类型检查器
# 安装
uv pip install mypy
# 检查类型
mypy myproject/
# 配置
[tool.mypy]
python_version = "3.11"
strict = true
warn_return_any = true
warn_unused_configs = true
类型提示示例:
from typing import Protocol, TypeVar, Generic
from collections.abc import Sequence, Callable
# 基本类型提示
def greet(name: str) -> str:
return f"Hello, {name}"
# 集合
def process_items(items: list[int]) -> dict[str, int]:
return {'total': sum(items), 'count': len(items)}
# 可选值
from typing import Optional
def find_user(user_id: int) -> Optional[dict]:
"""返回用户字典或如果未找到则返回None。"""
# ...
# 联合类型(Python 3.10+)
def parse_value(value: str | int) -> float:
return float(value)
# 可调用
def apply_function(func: Callable[[int], int], value: int) -> int:
return func(value)
# 泛型类型
T = TypeVar('T')
def first_element(items: Sequence[T]) -> T | None:
return items[0] if items else None
# 协议(结构子类型)
class Drawable(Protocol):
def draw(self) -> None:
...
def render(obj: Drawable) -> None:
obj.draw()
最佳实践工作流
当编写新代码时:
- 从类型提示和文档字符串开始
- 先写测试(TDD) - 定义预期行为
- 实现最少代码 以通过测试
- 重构 同时保持测试通过
- 运行代码检查器(
ruff check) - 格式化代码(
ruff format或black) - 检查类型(
mypy) - 运行测试(
pytest)
当重构现有代码时:
- 添加测试 如果不存在
- 运行现有测试 以建立基线
- 增量重构(小改动)
- 每次更改后运行测试
- 提高类型覆盖率
- 应用代码检查器修复
- 更新文档
常见模式
配置管理:
from dataclasses import dataclass
from pathlib import Path
import tomllib
@dataclass
class Config:
"""应用程序配置。"""
database_url: str
api_key: str
timeout: int = 30
debug: bool = False
def load_config(config_path: Path) -> Config:
"""从TOML文件加载配置。"""
with config_path.open('rb') as f:
data = tomllib.load(f)
return Config(**data)
日志记录:
import logging
from pathlib import Path
def setup_logging(log_level: str = "INFO", log_file: Path | None = None) -> None:
"""配置应用程序日志记录。"""
handlers: list[logging.Handler] = [logging.StreamHandler()]
if log_file:
handlers.append(logging.FileHandler(log_file))
logging.basicConfig(
level=log_level,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=handlers
)
# 用法
logger = logging.getLogger(__name__)
logger.info("应用程序启动")
logger.error("发生错误", exc_info=True)
使用argparse的CLI:
import argparse
from pathlib import Path
def parse_args() -> argparse.Namespace:
"""解析命令行参数。"""
parser = argparse.ArgumentParser(description="处理数据文件")
parser.add_argument('input', type=Path, help="输入文件路径")
parser.add_argument('-o', '--output', type=Path, help="输出文件路径")
parser.add_argument('-v', '--verbose', action='store_true', help="详细输出")
return parser.parse_args()
def main() -> None:
"""主入口点。"""
args = parse_args()
if args.verbose:
setup_logging("DEBUG")
process_file(args.input, args.output)
if __name__ == '__main__':
main()
代码审查说明
审查或生成Python代码时,检查:
-
PEP 8合规性:
- 正确的命名约定
- 适当的缩进(4个空格)
- 合适的行长度
- 正确的导入排序
-
类型提示:
- 所有函数签名都有类型提示
- 指定了返回类型
- 复杂类型使用适当的类型构造
-
文档:
- 所有公共函数都有文档字符串
- 文档字符串包括参数、返回、抛出部分
- 复杂逻辑有解释性注释
-
错误处理:
- 使用特定异常
- 资源适当清理
- 错误信息具有信息性
-
测试:
- 新功能有测试
- 覆盖边界情况
- 测试清晰且可维护
-
代码质量:
- 无代码重复
- 函数具有单一职责
- 魔法数字替换为命名常量
- 无过于复杂函数(考虑圈复杂度)
资源和工具
基本工具:
- uv:快速包安装器和解析器
- Ruff:快速Python代码检查和格式化器(基于Rust)
- Black:意见化代码格式化器
- Mypy:静态类型检查器
- Pytest:测试框架
- Pre-commit:代码质量的Git钩子
官方参考:
- PEP 8: https://peps.python.org/pep-0008/
- PEP 257: 文档字符串约定
- Python类型提示: PEP 484, 585, 604
- Python增强提案: https://peps.python.org/
限制
此技能专注于通用Python最佳实践。对于专门领域:
- 科学计算:考虑numpy/scipy约定
- Web开发:框架特定模式(Django、FastAPI)
- 数据科学:Jupyter笔记本最佳实践
- 异步编程:asyncio模式和最佳实践
对于这些专门领域,结合此技能与领域特定技能或文档。