目录

Python 代码风格与 Linting

学习目标:掌握 PEP 8 代码风格规范,学会使用 Black、Ruff、isort 等工具自动格式化和检查代码质量。


1. PEP 8 规范

PEP 8 是 Python 官方代码风格指南,是大多数 Python 项目遵循的标准。

1.1 命名规范

# 变量和函数:snake_case
user_name = "张三"
def calculate_total(price, quantity):
    pass

# 类:PascalCase
class UserProfile:
    pass

# 常量:UPPER_SNAKE_CASE
MAX_CONNECTIONS = 100
DEFAULT_TIMEOUT = 30

# 私有成员:_前缀
class MyClass:
    def __init__(self):
        self.public_var = "public"
        self._private_var = "private"
        self.__name_mangled = "mangled"  # 名称改写

# 特殊方法:双下划线
class Book:
    def __init__(self):
        pass
    def __str__(self):
        pass

# 模块级:snake_case
# 文件名:my_module.py

1.2 缩进与空格

# 4 空格缩进(不用 Tab)
def example():
    if True:
        print("4 空格")
        for i in range(10):
            print(i)

# 运算符两侧空格
x = 1 + 2
y = x * 3 - 1

# 逗号后空格
items = [1, 2, 3, 4]
config = {"name": "test", "value": 42}

# 函数参数默认值:等号两侧无空格
def greet(name="World", times=1):
    for _ in range(times):
        print(f"Hello, {name}")

# 关键字参数调用:等号两侧无空格
greet(name="Alice", times=3)

1.3 行长度与换行

# 每行不超过 79 字符(代码)或 72 字符(注释/文档字符串)
# 黑色(Black)默认 88 字符

# 隐式续行(括号内自动续行)
result = (first_value
          + second_value
          - third_value)

# 显式续行(反斜杠,不推荐)
total = first_value + \
        second_value + \
        third_value

# 函数调用换行
long_function_call(
    arg1, arg2, arg3,
    arg4, arg5, arg6
)

# 链式调用换行
result = (query
          .filter(User.active == True)
          .order_by(User.name)
          .limit(10))

1.4 导入规范

# 标准库优先
import os
import sys
from datetime import datetime

# 空行后是第三方库
import requests
from flask import Flask

# 空行后是本地模块
from myproject.models import User
from myproject.utils import helper

# 避免通配符导入
from os import *  # ✗ 不推荐

# 推荐
from os import path, getenv  # ✓

2. Black(代码格式化)

2.1 安装与使用

# 安装
pip install black

# 格式化单个文件
black script.py

# 格式化整个目录
black src/

# 检查但不修改(CI 中使用)
black --check src/

# 显示差异
black --diff src/

2.2 配置

# pyproject.toml
[tool.black]
line-length = 88
target-version = ['py311']
include = '\.pyi?$'
exclude = '''
/(
    \.git
  | \.venv
  | build
  | dist
  | migrations
)/
'''

2.3 格式化效果

# 格式化前
def foo(  x,  y ,z=5 ):
    result=x+y+z
    if result>10:return  True
    else :return False

# Black 格式化后
def foo(x, y, z=5):
    result = x + y + z
    if result > 10:
        return True
    else:
        return False

3. Ruff(极速 Linter)

3.1 安装与使用

# 安装(Rust 编写,极快)
pip install ruff

# 检查代码
ruff check src/

# 自动修复
ruff check --fix src/

# 格式化代码(替代 Black)
ruff format src/

3.2 配置

# pyproject.toml
[tool.ruff]
line-length = 88
target-version = "py311"

[tool.ruff.lint]
# 启用的规则集
select = [
    "E",    # pycodestyle errors
    "W",    # pycodestyle warnings
    "F",    # pyflakes
    "I",    # isort
    "N",    # pep8-naming
    "UP",   # pyupgrade
    "B",    # flake8-bugbear
    "SIM",  # flake8-simplify
    "C4",   # flake8-comprehensions
]
ignore = [
    "E501",  # 行太长(由 Black 处理)
]

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["S101"]  # 测试中允许 assert

[tool.ruff.lint.isort]
known-first-party = ["myproject"]

3.3 常见规则

# E: pycodestyle 错误
import os, sys  # E401: 多个导入在一行
# 修复:
import os
import sys

# F: pyflakes
import unused_module  # F401: 导入未使用
# 修复: 删除该导入

# I: isort(导入排序)
import sys
import os  # I001: 导入顺序错误
# 修复:
import os
import sys

# B: bugbear
def mutable_default(items=[]):  # B006: 可变默认参数
    pass
# 修复:
def mutable_default(items=None):
    if items is None:
        items = []

# C4: comprehensions
result = [x for x in [1, 2, 3]]  # 等价 list([1,2,3])
# C4 建议:
result = list([1, 2, 3])
# 或更优:
result = [1, 2, 3]

# SIM: simplify
if len(items) == 0:  # SIM102
    pass
# 简化为:
if not items:
    pass

4. isort(导入排序)

# 安装
pip install isort

# 排序导入
isort src/

# 检查但不修改
isort --check-only src/

# 显示差异
isort --diff src/
# pyproject.toml
[tool.isort]
profile = "black"
line_length = 88
known_first_party = ["myproject"]
known_third_party = ["django", "flask", "requests"]
# 排序前
from myproject.models import User
import sys
from flask import Flask
import os
from datetime import datetime

# isort 排序后
import os
import sys
from datetime import datetime

from flask import Flask

from myproject.models import User

5. pre-commit(提交前检查)

5.1 安装与配置

# 安装
pip install pre-commit

# 创建配置文件
# .pre-commit-config.yaml
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files
      - id: check-merge-conflict

  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.1.9
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.7.1
    hooks:
      - id: mypy
        additional_dependencies: [types-requests]

5.2 使用

# 安装 git hooks
pre-commit install

# 手动运行所有文件
pre-commit run --all-files

# 提交时自动触发检查
git add .
git commit -m "feat: add new feature"
# 如果检查失败,提交会被阻止

6. 常见代码坏味道

6.1 应该避免的模式

# 1. 魔法数字
# ✗ 不好
if user_age >= 18:
    pass

# ✓ 好
LEGAL_AGE = 18
if user_age >= LEGAL_AGE:
    pass

# 2. 过长函数
# ✗ 不好:一个函数做了太多事
def process_user(data):
    # 验证...(20行)
    # 保存...(15行)
    # 发邮件...(10行)
    # 记录日志...(5行)
    pass

# ✓ 好:拆分成小函数
def validate_user_data(data): ...
def save_user(data): ...
def send_welcome_email(user): ...
def log_user_creation(user): ...

# 3. 嵌套过深
# ✗ 不好
def process(items):
    for item in items:
        if item.is_valid:
            if item.type == "A":
                if item.value > 100:
                    # 处理逻辑
                    pass

# ✓ 好:提前返回
def process(items):
    for item in items:
        if not item.is_valid:
            continue
        if item.type != "A":
            continue
        if item.value <= 100:
            continue
        # 处理逻辑

# 4. 重复代码
# ✗ 不好
def get_user_name(user_id):
    conn = connect_db()
    cursor = conn.cursor()
    cursor.execute("SELECT name FROM users WHERE id = ?", (user_id,))
    result = cursor.fetchone()
    conn.close()
    return result[0] if result else None

def get_user_email(user_id):
    conn = connect_db()
    cursor = conn.cursor()
    cursor.execute("SELECT email FROM users WHERE id = ?", (user_id,))
    result = cursor.fetchone()
    conn.close()
    return result[0] if result else None

# ✓ 好:提取公共逻辑
def get_user_field(user_id, field):
    conn = connect_db()
    try:
        cursor = conn.cursor()
        cursor.execute(
            f"SELECT {field} FROM users WHERE id = ?", (user_id,)
        )
        result = cursor.fetchone()
        return result[0] if result else None
    finally:
        conn.close()

7. 工具链对比

工具 功能 速度 推荐度
Black 格式化 ★★★★★
Ruff Lint + Format 极快 ★★★★★
flake8 Lint 中等 ★★★☆☆
isort 导入排序 ★★★★☆
pylint 深度分析 ★★★☆☆
mypy 类型检查 中等 ★★★★★

推荐组合

现代 Python 项目工具链:
├── Ruff(替代 flake8 + isort + Black)
├── mypy(类型检查)
└── pre-commit(自动化)

8. VS Code 配置

// .vscode/settings.json
{
    "python.formatting.provider": "none",
    "editor.formatOnSave": true,
    "[python]": {
        "editor.defaultFormatter": "charliermarsh.ruff",
        "editor.codeActionsOnSave": {
            "source.fixAll.ruff": "explicit",
            "source.organizeImports.ruff": "explicit"
        }
    },
    "python.linting.enabled": true,
    "python.linting.mypyEnabled": true,
    "python.analysis.typeCheckingMode": "basic"
}

9. 小结

工具 用途 命令
PEP 8 风格规范 参考文档
Black 自动格式化 black src/
Ruff Lint + Format ruff check src/
isort 导入排序 isort src/
mypy 类型检查 mypy src/
pre-commit 提交前检查 pre-commit run

10. 练习题

  1. 使用 Ruff 检查你之前写的代码,修复所有警告。
  2. 为项目配置 pre-commit,包含 Ruff 和 mypy。
  3. 找出并修复代码中的 3 个 “坏味道”。
  4. 配置 VS Code 的自动格式化和类型检查。

下节预告:我们将学习调试技巧和性能分析,掌握排查和优化 Python 程序的方法。