Python最佳实践技能Skill python-best-practices

Python最佳实践技能提供全面的专家指导,帮助开发者编写高质量、可维护的Python代码,遵循行业标准。涵盖PEP 8代码风格、模块化设计、测试驱动开发(TDD)、类型提示、错误处理以及现代工具链配置,适用于新项目开发、代码重构、测试实施和代码质量提升。关键词:Python编程、代码最佳实践、PEP 8合规、单元测试、类型安全、代码重构、开发工具、软件质量、可维护性。

其他 0 次安装 1 次浏览 更新于 3/12/2026

名称: 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()

最佳实践工作流

当编写新代码时:

  1. 从类型提示和文档字符串开始
  2. 先写测试(TDD) - 定义预期行为
  3. 实现最少代码 以通过测试
  4. 重构 同时保持测试通过
  5. 运行代码检查器ruff check
  6. 格式化代码ruff formatblack
  7. 检查类型mypy
  8. 运行测试pytest

当重构现有代码时:

  1. 添加测试 如果不存在
  2. 运行现有测试 以建立基线
  3. 增量重构(小改动)
  4. 每次更改后运行测试
  5. 提高类型覆盖率
  6. 应用代码检查器修复
  7. 更新文档

常见模式

配置管理:

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代码时,检查:

  1. PEP 8合规性

    • 正确的命名约定
    • 适当的缩进(4个空格)
    • 合适的行长度
    • 正确的导入排序
  2. 类型提示

    • 所有函数签名都有类型提示
    • 指定了返回类型
    • 复杂类型使用适当的类型构造
  3. 文档

    • 所有公共函数都有文档字符串
    • 文档字符串包括参数、返回、抛出部分
    • 复杂逻辑有解释性注释
  4. 错误处理

    • 使用特定异常
    • 资源适当清理
    • 错误信息具有信息性
  5. 测试

    • 新功能有测试
    • 覆盖边界情况
    • 测试清晰且可维护
  6. 代码质量

    • 无代码重复
    • 函数具有单一职责
    • 魔法数字替换为命名常量
    • 无过于复杂函数(考虑圈复杂度)

资源和工具

基本工具:

  • uv:快速包安装器和解析器
  • Ruff:快速Python代码检查和格式化器(基于Rust)
  • Black:意见化代码格式化器
  • Mypy:静态类型检查器
  • Pytest:测试框架
  • Pre-commit:代码质量的Git钩子

官方参考:

限制

此技能专注于通用Python最佳实践。对于专门领域:

  • 科学计算:考虑numpy/scipy约定
  • Web开发:框架特定模式(Django、FastAPI)
  • 数据科学:Jupyter笔记本最佳实践
  • 异步编程:asyncio模式和最佳实践

对于这些专门领域,结合此技能与领域特定技能或文档。