✨ 29. 最佳实践

本章概述

Python 开发最佳实践:代码规范、项目结构、测试、调试、性能优化等。 预计学习时间:60 分钟


29.1 代码规范 PEP8

命名规范

类型规范示例
模块/包小写,下划线my_module, my_package
类名大驼峰MyClass, Person
函数/方法小写,下划线my_function, get_name
变量小写,下划线my_var, user_name
常量全大写,下划线MAX_SIZE, DEFAULT_VALUE
私有属性/方法单下划线开头_private_var, _internal_method

缩进

  • 用 4 个空格缩进
  • 不要用 Tab
  • 同一代码块缩进要一致
# ✅ 正确
if True:
    print("hello")
    print("world")
 
# ❌ 错误
if True:
  print("hello")  # 2个空格
    print("world")  # 4个空格

行长度

  • 每行不超过 79 个字符(PEP8)
  • 实际开发中常用 88 或 120
  • 太长的行要换行
# ✅ 正确:括号内换行
result = some_function(
    arg1,
    arg2,
    arg3
)
 
# ✅ 正确:反斜杠换行(不推荐)
long_string = "这是一段非常非常非常非常非常非常" \
              "长的字符串"

空行

  • 函数之间空两行
  • 类的方法之间空一行
  • 逻辑块之间空一行
def func1():
    pass
 
 
def func2():
    pass
 
 
class MyClass:
    def method1(self):
        pass
 
    def method2(self):
        pass

空格

# ✅ 正确
x = 1
y = 2
if x == 1:
    print(x)
 
# 函数参数
def func(a, b, c=10):
    pass
 
# 列表、字典
lst = [1, 2, 3]
d = {"a": 1, "b": 2}
 
# ❌ 错误
x=1
if x==1:
    print(x)

导入

# ✅ 正确:按顺序分组
# 1. 标准库
import os
import sys
 
# 2. 第三方库
import requests
import numpy as np
 
# 3. 本地模块
from my_module import my_function
 
# 每组之间空一行

自动格式化工具

  • black:代码格式化
  • flake8:代码检查
  • pylint:代码质量分析
  • isort:导入排序

29.2 项目结构

标准项目结构

my_project/
├── my_project/          # 主包
│   ├── __init__.py
│   ├── main.py          # 入口
│   ├── module1.py
│   ├── module2.py
│   └── utils/           # 工具包
│       ├── __init__.py
│       └── helpers.py
├── tests/               # 测试
│   ├── __init__.py
│   ├── test_module1.py
│   └── test_module2.py
├── docs/                # 文档
├── data/                # 数据文件
├── examples/            # 示例
├── requirements.txt     # 依赖
├── setup.py             # 安装配置
├── README.md            # 项目说明
└── .gitignore           # Git忽略

requirements.txt

# 项目依赖
requests==2.31.0
numpy>=1.24.0
pandas<2.0.0

安装依赖:

pip install -r requirements.txt

生成依赖:

pip freeze > requirements.txt

29.3 文档字符串

函数文档字符串

def add(a, b):
    """两个数相加。
 
    Args:
        a: 第一个数
        b: 第二个数
 
    Returns:
        两个数的和
 
    Examples:
        >>> add(1, 2)
        3
    """
    return a + b

类文档字符串

class Person:
    """人类。
 
    Attributes:
        name: 姓名
        age: 年龄
    """
 
    def __init__(self, name, age):
        """初始化。
 
        Args:
            name: 姓名
            age: 年龄
        """
        self.name = name
        self.age = age

文档字符串格式

  • Google 风格(上面的例子)
  • NumPy 风格
  • reStructuredText 风格

29.4 单元测试

pytest

安装

pip install pytest

编写测试

# test_math.py
 
def add(a, b):
    return a + b
 
def test_add():
    assert add(1, 2) == 3
    assert add(-1, 1) == 0
    assert add(0, 0) == 0
 
def test_add_negative():
    assert add(-1, -1) == -2

运行测试

pytest test_math.py
pytest tests/           # 运行目录下所有测试
pytest -v               # 详细输出
pytest -k "test_add"    # 按名称筛选

常用断言

assert x == y      # 相等
assert x != y      # 不等
assert x > y       # 大于
assert x in lst    # 包含
assert x is None   # 是None
assert bool(x)     # 为真

测试异常

import pytest
 
def divide(a, b):
    if b == 0:
        raise ValueError("除数不能为零")
    return a / b
 
def test_divide_zero():
    with pytest.raises(ValueError):
        divide(10, 0)

fixture

import pytest
 
@pytest.fixture
def user():
    return {"name": "张三", "age": 18}
 
def test_user_name(user):
    assert user["name"] == "张三"
 
def test_user_age(user):
    assert user["age"] == 18

29.5 调试技巧

最简单的调试方法:

def func(x):
    print(f"x = {x}")  # 打印变量
    result = x * 2
    print(f"result = {result}")
    return result

logging 模块

更专业的日志记录:

import logging
 
# 配置日志
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
 
logger = logging.getLogger(__name__)
 
def func(x):
    logger.debug(f"x = {x}")
    logger.info("函数开始执行")
    logger.warning("这是一个警告")
    logger.error("这是一个错误")

日志级别(从低到高):

  • DEBUG:调试信息
  • INFO:一般信息
  • WARNING:警告
  • ERROR:错误
  • CRITICAL:严重错误

pdb 调试器

Python 内置的调试器:

import pdb
 
def func(x):
    y = x * 2
    pdb.set_trace()  # 设置断点
    z = y + 10
    return z
 
func(5)

常用命令:

  • n:下一行
  • s:进入函数
  • c:继续执行
  • p 变量:打印变量
  • l:查看代码
  • q:退出

29.6 性能优化

时间复杂度

操作列表字典集合
查找O(n)O(1)O(1)
插入O(n)O(1)O(1)
删除O(n)O(1)O(1)

选择合适的数据结构

  • 需要频繁查找:用字典或集合
  • 需要顺序:用列表
  • 需要去重:用集合

生成器节省内存

# ❌ 列表:一次性生成所有元素,占内存
def get_numbers(n):
    return [i for i in range(n)]
 
# ✅ 生成器:逐个生成,省内存
def get_numbers(n):
    for i in range(n):
        yield i

字符串拼接

# ❌ 慢:每次拼接都创建新字符串
result = ""
for s in strings:
    result += s
 
# ✅ 快:join一次性拼接
result = "".join(strings)

列表推导式

# ✅ 列表推导式比for循环快
result = [x**2 for x in range(1000)]
 
# ❌ for循环慢
result = []
for x in range(1000):
    result.append(x**2)

内置函数

# ✅ 用内置函数,C实现,速度快
sum(lst)
max(lst)
min(lst)
sorted(lst)
any(lst)
all(lst)

性能分析

import cProfile
 
def slow_function():
    # 你的代码
    pass
 
cProfile.run("slow_function()")

或者用 timeit

import timeit
 
def func():
    return [x**2 for x in range(1000)]
 
# 测试运行时间
t = timeit.timeit(func, number=1000)
print(f"平均耗时:{t/1000:.6f}秒")

29.7 常见错误避免

不要用裸 except

# ❌ 不好:捕获所有异常
try:
    ...
except:
    pass
 
# ✅ 好:只捕获你能处理的
try:
    ...
except ValueError:
    ...
except FileNotFoundError:
    ...

不要用可变默认参数

# ❌ 不好:可变默认参数
def func(lst=[]):
    lst.append(1)
    return lst
 
# ✅ 好:用None
def func(lst=None):
    if lst is None:
        lst = []
    lst.append(1)
    return lst

不要用 == None

# ❌ 不好
if x == None:
    ...
 
# ✅ 好
if x is None:
    ...

不要用 eval

# ❌ 危险:安全风险
result = eval(user_input)
 
# ✅ 好:用具体的转换
result = int(user_input)

29.8 Pythonic 写法

遍历

# ❌ 不 Pythonic
for i in range(len(lst)):
    print(lst[i])
 
# ✅ Pythonic
for item in lst:
    print(item)
 
# 需要索引
for i, item in enumerate(lst):
    print(i, item)

字典遍历

# ❌ 不 Pythonic
for key in d:
    print(key, d[key])
 
# ✅ Pythonic
for key, value in d.items():
    print(key, value)

交换变量

# ❌ 不 Pythonic
temp = a
a = b
b = temp
 
# ✅ Pythonic
a, b = b, a

真值判断

# ❌ 不 Pythonic
if len(lst) == 0:
    ...
 
if x == True:
    ...
 
# ✅ Pythonic
if not lst:
    ...
 
if x:
    ...

上下文管理器

# ❌ 不 Pythonic
f = open("file.txt", "r")
content = f.read()
f.close()
 
# ✅ Pythonic
with open("file.txt", "r") as f:
    content = f.read()

🔗 相关章节


📝 我的笔记

在这里记录你的最佳实践心得

# 你的实践代码
 

✅ 本章检查清单

  • 了解 PEP8 代码规范
  • 知道标准的项目结构
  • 会写文档字符串
  • 会用 pytest 写单元测试
  • 了解基本的调试技巧
  • 知道常见的性能优化方法
  • 了解 Pythonic 的写法
  • 知道常见的错误避免方法