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.py1.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 False3. 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:
pass4. 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 User5. 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. 练习题
- 使用 Ruff 检查你之前写的代码,修复所有警告。
- 为项目配置 pre-commit,包含 Ruff 和 mypy。
- 找出并修复代码中的 3 个 “坏味道”。
- 配置 VS Code 的自动格式化和类型检查。
下节预告:我们将学习调试技巧和性能分析,掌握排查和优化 Python 程序的方法。