在现代 Python 项目中,使用多种工具来自动化代码风格检查、格式化和静态分析是一种常见的最佳实践。这些工具可以在代码提交前捕获问题,确保代码库的一致性和质量。
pre-commit 是一个用于管理 Git 预提交钩子(pre-commit hooks)的框架。它允许开发者在每次代码提交前自动运行一系列检查,从而充当代码质量的第一道防线[gatlenculp.medium.com]。
通过 .pre-commit-config.yaml
文件配置需要启用的钩子列表,pre-commit 会在提交时依次执行这些钩子脚本或命令。如果任意检查失败,提交将被拒绝,提示开发者修复问题[dev.to]。
核心作用: pre-commit 本身不直接进行代码检查或格式化,而是管理其他工具的集成。它支持众多插件和钩子,能够与各类代码检查器、格式化工具和静态分析工具配合使用[dev.to]。其作用在于确保代码在提交前符合项目设定的规范,例如统一代码风格、捕获语法错误或潜在安全漏洞等,从而提升团队协作效率和代码质量。
手动使用方式: 首先安装 pre-commit:
shellpip install pre-commit
然后在项目根目录创建 .pre-commit-config.yaml
文件,列出需要启用的钩子。例如:
yamlrepos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- repo: https://github.com/psf/black
rev: 24.3.0
hooks:
- id: black
args: [--line-length=88]
安装 Git 钩子脚本:
shellpre-commit install
之后,每次执行 git commit
时都会触发配置的钩子检查。也可以手动运行所有钩子检查:
shellpre-commit run --all-files
如果不需使用可以执行以下命令进行hooks卸载:
pre-commit clean
配置最佳实践: 在 .pre-commit-config.yaml
中,为每个钩子指定稳定的版本(rev
)以确保一致性。可以通过 args
传递工具的额外参数,例如设置代码行长度等。对于不需要每次提交都检查的文件,可以使用 exclude
正则排除。常用的钩子包括:
trailing-whitespace
:检查并去除行尾多余空格。end-of-file-fixer
:确保文件以换行符结尾。check-yaml
:校验 YAML 文件格式。check-added-large-files
:防止大文件被加入版本控制。black
:代码格式化工具。isort
:导入排序工具。flake8
:代码风格和错误检查。mypy
:类型检查。bandit
:安全漏洞扫描等。通过合理组合这些钩子,可以在提交前自动执行代码格式化、风格检查、类型检查和安全扫描等操作,保证代码库的规范和质量[gatlenculp.medium.com]。
Pre-commit 集成方式: 由于 pre-commit 本身就是管理预提交钩子的工具,集成其他工具通常是在 .pre-commit-config.yaml
中添加对应钩子的仓库和 ID。例如,集成 black
格式化工具的配置片段如下:
yaml- repo: https://github.com/psf/black
rev: 24.3.0 # 指定版本
hooks:
- id: black
args: [--line-length=88] # 传递参数
更多钩子的配置可以参考 [pre-commit.com] 官方文档和钩子仓库。
徽章: pre-commit 项目本身提供徽章,可在 README 中显示项目已启用 pre-commit 钩子:
markdown[](https://github.com/pre-commit/pre-commit)
将其添加到项目 README 以表明代码提交前会自动运行检查。
冲突解决: pre-commit 框架本身一般不会与其他工具冲突,因为它只是调用其他工具。但需要注意钩子的执行顺序和配置。例如,如果多个钩子都修改代码(如格式化工具和导入排序工具),应确保它们按正确顺序执行,避免相互覆盖。通常先运行修改代码的钩子(如格式化、排序),再运行只检查不修改的钩子(如风格检查、类型检查),以确保前者的修改能被后者正确验证。此外,不同工具的配置应保持一致(例如行宽限制),防止同一问题在不同钩子中出现矛盾提示。通过合理配置钩子顺序和参数,可以避免大多数冲突。
isort 是一个用于自动排序 Python 导入语句的工具。它可以将导入按字母顺序排列,并自动按类型和模块来源进行分组(例如标准库、第三方库、本地应用库等)isort官网。使用 isort 可以使代码中的 import
语句保持一致的顺序和结构,提高可读性。
isort 的主要功能是规范导入顺序。在大型项目中,手动维护导入的顺序容易出错且耗时,isort 则可以自动完成这一任务。通过将导入按标准库、第三方库、本地模块等分类排序,isort 确保代码中的导入块结构清晰、易于浏览。
安装 isort:
shellpip install isort
对指定文件或目录运行 isort 进行导入排序:
shellisort myfile.py # 格式化单个文件 isort mypackage/ # 递归格式化整个包
默认情况下,isort 会直接修改文件内容。可以使用 --check
或 -c
参数仅检查不修改,此时 isort 会输出不符合排序规范的文件列表。也可以使用 --diff
查看排序前后的差异而不实际修改文件。
isort 支持通过 pyproject.toml
或 .isort.cfg
等文件进行配置。在 pyproject.toml
中,推荐将配置放在 [tool.isort]
部分,并添加注释解释关键配置项的作用。以下是一个示例:
toml[tool.isort]
profile = "black" # 采用与 Black 兼容的配置文件
line_length = 88 # 导入行最大长度(与 Black 一致)
multi_line_output = 3 # 多行导入的输出格式(3表示括号换行)
include_trailing_comma = true # 为多行导入添加尾逗号(减少 git diff)
force_grid_wrap = 0 # 不强制网格换行
use_parentheses = true # 总是使用圆括号包裹多行导入
ensure_newline_before_comments = true # 在导入后注释前添加空行
配置说明:
profile = "black"
:使用 isort 内置的 black 配置文件,使 isort 的风格与 Black 格式化器兼容[pycqa.github.io]。这会应用一组优化的默认值(例如导入后添加尾逗号等)以减少与 Black 的冲突。line_length = 88
:设置导入语句的最大行宽,通常与代码格式化工具(如 Black)的行宽一致,避免格式化后换行不一致。multi_line_output = 3
:指定多行导入时的输出样式(0-12种选项)。值为3表示使用圆括号将导入项换行排列(类似 Black 风格)[pycqa.github.io]。include_trailing_comma = true
:在多行导入的最后一项后添加逗号。这有助于生成更简洁的 git diff(新增或删除导入时只有一行变化)[pycqa.github.io]。force_grid_wrap = 0
:设置为0表示当导入项超过行宽时,isort 会自动决定换行方式,而不强制特定列数的网格换行。use_parentheses = true
:强制所有多行导入使用圆括号包裹,而不是反斜杠续行。这与 Black 的风格一致,提高可读性。ensure_newline_before_comments = true
:确保在导入语句后的注释前有一个空行,以符合 PEP 8 对注释与代码间距的要求。通过上述配置,isort 将按照 Black 兼容的风格对导入进行排序和格式化,减少与代码格式化工具的冲突。如果需要进一步调整,isort 还提供丰富的选项,例如自定义导入分组、忽略特定文件等,可参考 [pycqa.github.io] 官方文档。
在 .pre-commit-config.yaml
中添加 isort 钩子:
yaml- repo: https://github.com/PyCQA/isort
rev: 5.13.2 # 使用稳定版本
hooks:
- id: isort
args: [--profile=black] # 传递与 Black 兼容的配置
这样每次提交前,isort 都会自动检查并排序导入。如果导入需要调整,isort 会修改文件并终止提交,提示重新提交修改后的内容。
isort 提供项目徽章,可在 README 中展示项目使用 isort 管理导入:
markdown[](https://pycqa.github.io/isort/)
将其添加到 README 以表明代码导入由 isort 自动管理。
isort 通常与代码格式化工具(如 Black)配合良好。但需注意两者的配置应协调:建议使用 isort 的 profile = "black"
配置,以确保 isort 生成的导入格式与 Black 兼容isort配置指南。如果不这样做,Black 可能会在 isort 排序后再次修改导入行的格式(例如添加括号或换行),导致不必要的改动。通过统一行宽和风格配置,可以避免 isort 与 Black 之间的冲突。另外,isort 与类型检查器 mypy 等工具通常没有直接冲突,可并行使用。
Black 是一款代码格式化工具。它以严格统一的规则自动格式化代码,使团队无需在代码风格上争论不休black官方文档。Black 会重新排版代码,包括行长度、缩进、空格、括号位置等所有细微格式,确保代码风格一致。使用 Black 后,开发者将把控制权交给工具,不再手动调整格式细节,从而节省时间和精力。
Black 的核心功能是强制统一代码风格。它严格按照自己的规则格式化代码,消除人为的格式差异。这带来的好处包括:
Black 支持的风格在大多数情况下符合 PEP 8 规范,但在某些细节上有自己的偏好(例如默认行宽88字符、括号换行等)。通过使用 Black,团队可以确保代码库的格式高度一致,提升可读性和可维护性。
安装 Black:
shellpip install black
对指定文件或目录运行 Black 进行格式化:
shellblack myfile.py # 格式化单个文件 black mypackage/ # 递归格式化整个包
Black 默认会直接修改文件内容。可以使用 --check
或 -c
参数仅检查不修改,此时若文件不符合 Black 格式会返回非零状态并列出文件。使用 --diff
可以查看格式化前后的差异而不修改文件。
Black 会对代码进行多方面的重新排版。以下是一些常见的格式化前后对比:
# a_messy_file.py def my_function(arg1, arg2, arg3,arg4, long_argument_name_for_testing): result = {'key1': 'value1', 'key2' : 'value2', 'key3':'value3'} if (arg1 > arg2 and arg3 < arg4): print("Condition met") return result
black
)# a_messy_file.py def my_function( arg1, arg2, arg3, arg4, long_argument_name_for_testing ): result = { "key1": "value1", "key2": "value2", "key3": "value3", } if arg1 > arg2 and arg3 < arg4: print("Condition met") return result
行长度与换行: Black 会将过长的行自动换行,并优先使用括号而非反斜杠续行。例如:
python# 格式化前
if some_long_condition1 \
and some_long_condition2:
do_something()
# 格式化后(Black)
if (
some_long_condition1
and some_long_condition2
):
do_something()
Black 移除了反斜杠,改用圆括号包裹条件,并在运算符前换行,使逻辑更清晰。
空格与逗号: Black 会在二元运算符周围添加空格,在逗号后添加空格,并移除不必要的空格。例如:
python# 格式化前
x=5
a = [1 ,2 ,3]
func( 'hello' , 'world' )
# 格式化后(Black)
x = 5
a = [1, 2, 3]
func('hello', 'world')
字符串引号: Black 统一将字符串引号改为单引号(除非字符串中包含单引号需要转义):
python# 格式化前
message = "Hello, world!"
# 格式化后(Black)
message = 'Hello, world!'
多行结构与尾逗号: 对于列表、元组、字典等结构,若拆分成多行,Black 会在每行元素后添加逗号,并将闭合括号单独成行:
python# 格式化前
my_list = [1, 2, 3,
4, 5, 6]
my_dict = {
'a': 1,
'b': 2
}
# 格式化后(Black)
my_list = [1, 2, 3, 4, 5, 6] # 单行容纳则合并
my_list = [
1,
2,
3,
4,
5,
6,
] # 若单行放不下则每个元素一行并加尾逗号
my_dict = {
'a': 1,
'b': 2,
}
添加尾逗号和独立闭合括号的做法减少了版本控制中的差异(新增或删除元素时只有一行变化)
导入语句: Black 会调整导入语句的格式,例如长的 from ... import
会换行并使用括号:
python# 格式化前
from mypackage import (a, b, c,
d, e, f)
# 格式化后(Black)
from mypackage import (
a,
b,
c,
d,
e,
f,
)
这种格式与 isort 配合良好,特别是在 isort 启用了尾逗号选项时
通过以上示例可以看出,Black 的格式化使代码变得整洁统一。虽然初次使用可能会对代码进行大量修改,但此后每次提交的格式差异将降至最低,代码审查将更关注逻辑而非风格
** Black 是一款高度 opiniated(有主见)的工具,它的配置选项非常有限,目的是鼓励开发者接受其统一风格black代码风格指引。不过,Black 允许通过 pyproject.toml
等方式进行少量配置。推荐在 [tool.black]
部分设置必要的选项并添加注释说明:
toml[tool.black]
line-length = 88 # 代码行最大长度(默认88,符合大多数项目)
target-version = ['py310'] # 目标支持的Python最小版本(可选)
include = '\.pyi?$' # 需要格式化的文件正则(默认已包含.py,这里示例包含.pyi)
exclude = '''
/(
\.git/ # 排除Git目录
| \.hg/ # 排除Mercurial目录
| \.mypy_cache/ # 排除mypy缓存
| \.tox/ # 排除tox目录
| \.venv/ # 排除虚拟环境
| _build/ # 排除构建目录
| buck-out/
| build/
| dist/
)/
''' # 需要排除的目录正则
配置说明:
line-length = 88
:设置代码行的最大字符数。Black 默认使用88字符,比传统的80字符稍宽,被认为在可读性和减少换行之间取得了良好平衡。除非项目有特殊要求,否则不建议更改此值,以与社区习惯保持一致。target-version = ['py310']
:指定项目支持的最低 Python 版本。Black 会根据此选项调整格式化行为(例如是否使用新的语法特性)。如果项目兼容多个版本,可列出如 ['py38', 'py39', 'py310']
。include
和 exclude
:这两个参数分别用正则表达式指定需要和不需要格式化的文件路径。include
默认已包含所有 .py
文件,除非需要额外包含其他类型文件(如 .pyi
存根文件),否则一般不需要修改。exclude
用于排除某些目录或文件,如版本控制系统目录、虚拟环境、构建输出等,避免工具对这些文件进行不必要的格式化。上述示例中的正则排除了常见的版本控制和缓存目录。Black 不支持配置诸如缩进风格(始终4空格)、运算符空格等细节,因为这些都由其严格的风格规则统一处理。这种极简配置哲学确保了无论项目新旧,格式化结果都一致可控。
在 .pre-commit-config.yaml
中添加 Black 钩子:
yaml- repo: https://github.com/psf/black
rev: 24.3.0 # 使用稳定版本
hooks:
- id: black
args: [--line-length=88] # 可传递配置参数,如行宽
这样每次提交前,Black 都会自动格式化代码。若代码需要格式化,Black 会修改文件并阻止提交,提示重新提交修改后的内容。
Black 提供项目徽章,可在 README 中展示项目使用 Black 格式化代码:
markdown[](https://github.com/psf/black)
将其添加到 README 以表明代码风格由 Black 统一管理。
冲突解决: Black 由于其严格的格式化规则,有时会与其他代码风格工具产生“冲突”,但多数情况下这种冲突是可以调和的:
--profile=black
配置,使 isort 输出的格式符合 Black 风格。这样 Black 通常不会再对 isort 排序后的导入做额外改动。此外,在 pre-commit 钩子中,应先运行 isort 再运行 Black,因为 isort 可能调整导入顺序,Black 最后统一格式可以确保最终输出符合规范。E501
(行过长)警告;Black 会在二元运算符前换行,这可能触发 Flake8 的 E226
等警告,也应在 Flake8 配置中忽略这些特定错误码。通过调整检查器的规则,可以避免对 Black 已处理的格式问题重复报错。总之,通过合理配置和调整其他工具的规则,Black 可以与多数常用工具和平共处。其带来的统一风格和效率提升通常远大于解决这些小冲突的成本。
mypy 是 Python 的静态类型检查器。它可以分析带有类型注解的 Python 代码,验证类型使用是否正确,帮助捕获潜在的类型错误官方文档。通过在开发过程中运行 mypy,开发者能够在不实际运行代码的情况下发现类型不匹配、错误的返回类型等问题,提高代码的健壮性和可维护性。
mypy 的核心功能是静态类型检查。它读取代码中的类型提示(type hints),并在编译(或运行 mypy)时检查这些类型是否一致。例如,当函数期望接收一个整数参数但实际传入字符串时,mypy 会报告类型错误。这种检查有助于在早期发现因类型误用导致的 bug,减少运行时异常。此外,mypy 还支持类型推断,即使没有显式注解,它也能推断变量类型并进行检查。通过使用 mypy,团队可以逐步为代码添加类型注解,构建类型安全的代码库,提高代码质量和可读性
安装 mypy:
shellpip install mypy
对指定文件或模块运行 mypy 检查:
shellmypy myfile.py # 检查单个文件 mypy mypackage/ # 递归检查整个包
mypy 会输出检查结果,列出发现的类型错误或警告。如果没有错误则输出为空并返回状态0。可以使用 --check-untyped-defs
等选项增加检查严格程度。在持续集成环境中,通常会将 mypy 作为构建步骤之一,确保类型检查通过。
mypy 的配置选项非常丰富,可通过 pyproject.toml
(在 [tool.mypy]
下)或 mypy.ini
文件进行设置。以下是一个推荐的 pyproject.toml
配置片段,包含关键选项及注释说明:
toml[tool.mypy]
strict = true # 启用严格模式,打开所有推荐的检查选项
show-error-codes = true # 在输出中显示错误代码,便于针对性忽略
warn-unused-ignores = true # 警告那些未生效的# type: ignore注释
disallow-any-generics = true # 不允许泛型类型参数为Any
disallow-subclassing-any = true # 不允许继承自Any类型
disallow-untyped-calls = true # 不允许调用未标注类型的函数
disallow-untyped-defs = true # 不允许定义未标注返回值和参数类型的函数
disallow-incomplete-defs = true # 不允许函数部分参数未标注类型(在strict模式下已包含)
disallow-untyped-decorators = true # 不允许装饰器修饰未完全标注的函数
no-implicit-optional = true # 不将未显式Optional的None参数推断为可选
warn-redundant-casts = true # 对冗余的类型转换发出警告
warn-unused-configs = true # 对配置中未使用的选项发出警告
allow-redefinition = true # 允许变量重新定义(某些动态代码需要)
cache-dir = ".mypy_cache" # 指定mypy缓存目录,避免污染项目根目录
配置说明:
strict = true
:启用严格模式。这会打开一系列推荐的检查选项,包括 disallow-untyped-defs
、no-implicit-optional
、warn-redundant-casts
等,最大程度保证类型安全。对于新项目或希望逐步严格化的项目,建议从严格模式开始。如果代码库类型注解尚不完整,严格模式可能报错较多,可以考虑先使用 strict = false
并按需启用个别选项,待逐步补全注解后再切换到严格模式。show-error-codes = true
:让 mypy 在错误信息中显示错误代码(如 error: Incompatible return value type [1]
中的 [1]
)。这有助于识别具体是哪类错误,并方便在配置或代码中针对某些错误码进行忽略或调整。warn-unused-ignores = true
:当代码中存在 # type: ignore
注释但实际上没有需要忽略的错误时,mypy 会给出警告。这可以防止不必要的 ignore
堆积,保持代码整洁。disallow-any-generics = true
:禁止在泛型类型(如 List
, Dict
等)中使用 Any
作为类型参数。这确保泛型的类型参数都是具体的,提高类型检查的有效性。disallow-subclassing-any = true
:不允许创建继承自 Any
的类型。这可以防止滥用 Any
作为基类的不良设计。disallow-untyped-calls = true
:不允许调用未进行类型标注的函数。如果调用了一个没有参数和返回类型注解的函数,mypy 将报错。这鼓励为所有函数添加类型提示,提高代码可读性和可维护性。disallow-untyped-defs = true
:不允许定义未标注类型的函数(即函数没有参数类型或返回类型注解)。这在严格模式下默认开启,要求所有函数都有完整的类型注解。对于存量代码,可能需要逐步补全注解或暂时使用 # type: ignore
忽略,但长远看应尽量满足此要求。no-implicit-optional = true
:禁止隐式的可选类型。当函数参数默认值为 None
时,要求显式使用 Optional[T]
注解类型,而不是由 mypy 推断为可选。这使代码意图更明确。warn-redundant-casts = true
:对冗余的类型转换发出警告。例如,如果变量已经是 int
类型,又显式转换为 int
,mypy 会提示这可能没必要。这有助于保持代码简洁。allow-redefinition = true
:允许变量在同一作用域内重新定义。默认情况下,mypy 会对同一变量多次赋值不同类型的情况报错。设置此项为 true
可以关闭该检查,这在某些动态代码或重构场景下有用。但需谨慎使用,以免掩盖潜在的类型问题。cache-dir = ".mypy_cache"
:指定 mypy 存储缓存的目录。mypy 会缓存分析结果以加速后续运行,默认缓存目录是 .mypy_cache
,可以通过此选项显式指定并建议将其加入 .gitignore
。以上配置提供了一个严格但实用的 mypy 检查环境。如果希望进一步调整,mypy 还有许多其他选项,例如 ignore-missing-imports
(忽略缺失导入的错误)、strict-equality
(启用严格的相等比较检查)等,可以根据项目需要启用。
在 .pre-commit-config.yaml
中添加 mypy 钩子:
yaml- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.14.0 # 使用与项目兼容的mypy版本
hooks:
- id: mypy
args: [--strict] # 传递严格模式参数
additional_dependencies: [pydantic==2.0] # 可选:如果项目依赖某些类型库,需要在此列出,mypy才能识别
由于 mypy 需要项目的依赖环境来解析类型(例如第三方库的类型存根),在 pre-commit 中集成时要确保虚拟环境中安装了所有依赖。如果某些依赖在钩子中不可用,可以使用 additional_dependencies
列出它们,pre-commit 会在运行 mypy 前安装这些包。这样每次提交前,mypy 都会检查代码的类型一致性,发现问题会阻止提交。
[](https://mypy-lang.org/)
mypy 作为静态类型检查器,通常与其他代码格式化或风格检查工具没有直接冲突。但需要注意以下几点:
Any
类型),mypy 可能不会捕获很多问题。这并非冲突,而是需要逐步改进代码的类型覆盖。可以通过 mypy 的配置选项(如 disallow-untyped-*
系列)来逐步强制添加注解。在这个过程中,可能会与开发者的工作习惯产生“冲突”,需要团队达成共识,逐步提升类型安全。.pyi
文件),mypy 可能会报错 Need type annotation for 'xxx'
等。这可以通过安装该库的类型包(通常名称类似 types-xxx
)或使用 # type: ignore
临时忽略。如果团队希望严格检查,应尽量为依赖库找到或提供类型存根,而不是长期忽略。这属于项目依赖管理和类型生态的问题,不是工具本身的冲突。总的来说,mypy 与其他工具配合良好。其带来的静态类型检查能力能够有效提升代码质量,与格式化、风格检查工具一起构成完整的代码质量保障体系。
mypy
vs. Pylance (Pyright)在现代 Python 开发中,尤其是在 VSCode 环境下,开发者经常会遇到 mypy
和 Pylance(其底层是微软的 Pyright 类型检查器)的选择问题。它们并非完全的替代关系,而是各有侧重,组合使用效果更佳。
mypy
: 是一个更经典、可配置性更强的命令行工具。它拥有更广泛的社区支持和插件生态(如 Django-mypy)。mypy
通常被用作一个更正式、更严格的检查关卡,例如在 pre-commit
或 CI/CD 流水线中,确保代码在合入主分支前通过最全面的类型检查。选择建议: 推荐组合使用。在 VSCode 中依赖 Pylance 获得流畅的实时开发体验,同时在
pre-commit
流程中集成mypy
作为最终的代码质量门禁。这种方式结合了 Pylance 的速度和mypy
的严格性与全面性。
PyCharm 内置了基于其自身引擎的类型检查功能。在 VSCode 中,Pylance 扩展提供了顶级的类型检查体验,同时也可以通过配置集成 mypy
作为额外的 linter。
bandit 是 Python 代码的安全漏洞静态分析工具。它通过解析代码的抽象语法树(AST),查找常见的安全隐患和编码错误官方文档。bandit 内置了一系列安全规则(插件),可以检测如硬编码的密码、危险的函数调用、不安全的加密实践等问题,帮助开发者在早期发现潜在的安全漏洞。
bandit 的核心功能是代码安全扫描。它充当一个“安全检查器”,在代码中寻找可能导致安全问题的模式。例如,bandit 会识别出使用 eval()
、pickle
反序列化、硬编码的密钥等高危操作,并给出警告。通过在开发流程中集成 bandit,可以在代码提交或部署前捕获常见的安全缺陷,防止将高危代码引入生产环境。bandit 尤其适用于大型项目或对安全性要求较高的项目,能够有效提升代码的安全合规性。
安装 bandit:
shellpip install bandit
对指定文件或目录运行 bandit 扫描:
shellbandit myfile.py # 扫描单个文件 bandit -r mypackage/ # 递归扫描整个包(-r表示递归)
bandit 会输出扫描结果,列出发现的安全问题及严重程度。每条结果包含问题编号(如 B301
表示 blacklist
类型的问题,对应 eval()
函数)、所在文件和行号,以及问题描述。默认输出为终端文本,也可以使用 -f
选项指定输出格式(如 -f json
生成JSON报告)。
bandit 可以通过配置文件(如 .bandit.yaml
或在 pyproject.toml
的 [tool.bandit]
部分)进行规则调整和忽略设置。以下是一个示例配置(这里以独立的 .bandit.yaml
为例,也可转换为 toml 格式):
yamltargets: ['.'] # 需要扫描的目标目录(当前目录)
exclude: # 需要排除的目录列表
- 'tests/' # 排除测试目录
- 'venv/' # 排除虚拟环境目录
skips: # 需要跳过的测试(规则)列表
- 'B101' # 跳过对assert语句的检查(B101)
- 'B306' # 跳过对mako模板的检查(B306)
- 'B308' # 跳过对HTTPSConnection的不安全验证检查(B308)
- 'B309' # 跳过对SSL/TLS证书验证的检查(B309)
- 'B601' # 跳过对subprocess调用的检查(B601)
- 'B701' # 跳过对Jinja2自动转义的检查(B701)
level: 'medium' # 报告问题的最低严重级别(可选:low, medium, high)
confidence: 'medium' # 报告问题的最低置信度(可选:low, medium, high)
配置说明:
targets
:指定要扫描的文件或目录。默认可以设为当前目录 '.'
,表示扫描整个项目。exclude
:列出需要排除的目录路径。例如,通常会排除 tests/
(测试代码)、venv/
或 .venv/
(虚拟环境)以及构建输出目录等,这些地方的代码一般不是主业务逻辑,且测试中可能故意使用一些“不安全”函数(如模拟)。skips
:列出要跳过的 bandit 测试编号。bandit 的规则有很多,有些可能不适用项目场景。例如 B101
检查 assert
语句,在生产代码中 assert
可能被优化掉,确实不应用于安全检查,所以可以跳过。B306
针对 mako
模板的安全,若项目不使用 mako 模板可跳过。B308
和 B309
涉及 SSL/TLS 连接的证书验证,如果项目中某些连接需要临时忽略证书验证(如内部测试环境),可以暂时跳过这些规则,但在生产环境应谨慎处理。B601
检查 subprocess
模块的使用,认为 subprocess.call
等可能有风险,如果项目广泛使用 subprocess 且已确保参数安全,可跳过此规则避免误报。B701
检查 Jinja2 模板是否开启自动转义,如果项目已统一开启自动转义,也可跳过。通过合理跳过不相关或误报较多的规则,可以减少干扰,让 bandit 报告更聚焦于真正的问题。level
和 confidence
:这两个参数控制报告的阈值。level
指定只报告严重级别不低于该值的问题(例如设为 medium
则忽略 low
级别的问题);confidence
指定只报告置信度不低于该值的问题。bandit 对每个问题会评估严重程度(如 LOW
, MEDIUM
, HIGH
)和置信度(表示判断该问题的确定程度)。根据项目安全要求,可以调整这些阈值。如果希望看到所有潜在问题,可设为 low
;如果希望只关注高置信度的严重问题,可设为 high
。上述配置示例提供了一个平衡:排除了无关目录和部分误报规则,只报告中等及以上严重程度的问题。在实际项目中,应根据自身情况调整 skips
、level
等参数。bandit 还支持在代码中通过注释忽略特定行的问题(如 # nosec B306
),这在某些特殊情况下需要绕过检查时使用。
在 .pre-commit-config.yaml
中添加 bandit 钩子:
yaml- repo: https://github.com/PyCQA/bandit
rev: 1.7.4 # 使用稳定版本
hooks:
- id: bandit
args: [-r, .] # -r表示递归扫描,.表示当前目录
exclude: ^(tests/|venv/) # 正则排除目录(与配置文件中的exclude作用类似)
这里假设项目根目录运行扫描。exclude
参数可以正则匹配要跳过的文件路径,与配置文件中的 exclude
列表作用相同。通过配置 bandit 钩子,每次提交前都会扫描代码的安全问题。如果发现高优先级的漏洞,提交将被阻止,从而促使开发者修复或确认这些问题。
bandit 提供项目徽章,可在 README 中展示项目使用 bandit 进行安全检查:
markdown[](https://github.com/PyCQA/bandit)
将其添加到 README 以表明代码经过 bandit 安全扫描。徽章颜色通常为黄色,提醒关注安全性。
bandit 作为安全检查工具,一般不会与其他代码工具产生直接冲突。但需要注意以下几点:
subprocess
调用或 eval
使用可能在项目中是合理且安全的,但 bandit 仍会标记。这时可以通过配置跳过相应规则(如上述 skips
列表)或在代码行添加 # nosec
注释忽略。需要在安全和开发效率之间取得平衡:既不遗漏真正的漏洞,也避免被大量误报干扰。总体而言,bandit 可以很容易地融入现有工作流中,与其他工具并行不悖。通过合理配置,它能有效提升代码的安全性,而不会对开发流程造成重大阻碍。
autoflake 是一个用于清理 Python 代码的小工具,主要功能是自动移除未使用的导入和未使用的变量官方仓库。在开发过程中,我们经常会导入一些模块或定义一些变量,后来不再使用但忘记删除,这会让代码显得冗余和杂乱。autoflake 可以检测并删除这些无用代码,使代码更简洁易读。此外,autoflake 默认还会移除多余的 pass
语句。
autoflake 的核心功能是代码清理。它充当代码“清洁工”,帮助开发者移除不再需要的代码元素。这包括:
pass
语句: 在空函数或类中,如果 pass
是唯一的语句且可以省略(Python 允许空块用缩进表示),autoflake 会将其删除。通过使用 autoflake,开发者可以保持代码库的整洁,减少因冗余代码导致的阅读干扰和潜在错误。尤其是在大型项目中,定期运行 autoflake 可以清理掉那些“幽灵”导入和变量,提高代码质量。
autoflake
)import os import sys # 未使用 def main(): x = 1 # 未使用 print("Hello")
autoflake
)import os def main(): print("Hello")
手动使用方式: 安装 autoflake:
shellpip install autoflake
运行 autoflake 清理指定文件或目录。例如,清理单个文件并原地修改:
shellautoflake --in-place --remove-unused-imports --remove-unused-variables myfile.py
常用参数说明:
--in-place
(或 -i
):直接在原文件上修改。--remove-unused-imports
:移除未使用的导入(默认仅对标准库模块有效,如需对所有模块生效需配合 --remove-all-unused-imports
。--remove-unused-variables
:移除未使用的变量(默认不启用,因为可能不安全。--remove-duplicate-keys
:移除字典字面量中重复的键(保留最后一个出现的)。--expand-star-imports
:展开 from module import *
为显式导入(需配合 --imports
指定模块)。--check
:检查哪些代码会被删除,但不实际修改文件。例如,要递归清理整个项目并备份原文件:
shellautoflake -i -r --remove-unused-imports --remove-unused-variables --backup .
(--backup
会为修改的文件创建 .bak
备份)
autoflake 支持通过配置文件(如 .autoflake
或 pyproject.toml
的 [tool.autoflake]
部分)设置默认参数。以下是一个 pyproject.toml
配置示例:
toml[tool.autoflake]
in_place = true # 默认启用原地修改
remove_unused_imports = true # 默认移除未使用的导入
remove_unused_variables = true # 默认移除未使用的变量
remove_duplicate_keys = true # 默认移除字典重复键
remove_ranges = false # 不启用移除指定范围代码的功能(除非特殊需要)
check = false # 默认不启用检查模式(直接修改)
include_star_imports = false # 不自动展开*导入(除非明确指定)
imports = [] # 未指定要展开*导入的模块列表
exclude = [ # 需要排除的文件/目录模式
'tests/*.py', # 排除测试文件
'setup.py', # 排除安装脚本
]
配置说明:
in_place = true
:设置为 true
表示 autoflake 默认会直接修改文件。如果设为 false
,则需要通过命令行 -i
参数才能修改文件。remove_unused_imports = true
:启用移除未使用导入的功能。注意默认情况下,autoflake 仅移除标准库中未使用的导入,第三方库的未使用导入不会被移除(出于安全考虑,因为第三方模块可能有副作用)。如果需要移除所有未使用导入(包括第三方),可以添加 remove_all_unused_imports = true
配置项(或使用命令行 --remove-all-unused-imports
)。remove_unused_variables = true
:启用移除未使用变量的功能。默认不启用,因为自动删除变量存在一定风险(例如某些框架可能通过反射使用变量)。启用后,autoflake 会删除函数参数和局部变量中未被使用的声明。使用时应谨慎,确保项目中没有通过字符串或反射引用这些变量的情况。remove_duplicate_keys = true
:启用移除字典字面量中重复键的功能。这将保留最后出现的键值对,删除之前的重复项。remove_ranges = false
:此选项用于指定要移除的代码行范围(例如 --remove-ranges=5-10
删除5-10行)。通常不需要在配置中启用,保持 false
即可。check = false
:设为 true
时,autoflake 不会修改文件,而是输出哪些导入/变量会被删除。这里配置为 false
表示默认直接执行修改。include_star_imports = false
:设为 true
时,如果配合 --expand-star-imports
,autoflake 会尝试展开 from module import *
为显式导入。但自动展开可能不完整,需要 --imports
指定模块。通常不建议自动展开星号导入,除非明确需要。imports = []
:如果启用了展开星号导入,这里列出要处理的模块名。例如 imports = ["os", "sys"]
表示遇到 from os import *
或 from sys import *
时尝试展开。exclude = [...]}
:列出需要排除的文件或目录模式。例如测试文件(tests/*.py
)通常包含未使用的导入(如测试框架导入但某些测试未用到),可以排除以避免干扰。setup.py
等脚本也可能不需要清理。通过排除这些文件,可以防止 autoflake 误删测试辅助代码或打包脚本中的内容。配置好后,运行 autoflake myfile.py
就会按上述选项执行,无需每次在命令行重复参数。对于大型项目,建议将 autoflake 集成到 pre-commit 或 CI 中定期运行,以保持代码整洁。
在 .pre-commit-config.yaml
中添加 autoflake 钩子:
yaml- repo: https://github.com/PyCQA/autoflake
rev: 2.1.1 # 使用稳定版本
hooks:
- id: autoflake
args: [
--in-place,
--remove-unused-imports,
--remove-unused-variables,
--remove-duplicate-keys,
]
exclude: ^(tests/|setup\.py)$ # 正则排除测试目录和setup.py
这样每次提交前,autoflake 都会自动清理未使用的导入和变量。如果有代码被修改,autoflake 会更新文件并阻止提交,提示重新提交修改后的内容。
autoflake 没有官方徽章,但可以在 README 中提及项目使用 autoflake 清理冗余代码
autoflake 通常不会与其他工具产生冲突,因为它处理的是代码中的冗余部分,而其他工具(格式化、检查)处理的是风格或正确性问题。需要注意的是:
List
, Dict
等类型需要从 typing
导入,如果代码中没有显式使用这些类型(仅在类型注解中使用),某些工具可能认为它们未被使用。autoflake 默认不会删除 typing
模块的导入,因为这些导入通常用于类型提示(autoflake 对 typing
模块有特殊处理,避免删除必要的类型导入。不过,如果使用了 from __future__ import annotations
或 Python 3.10+ 的标准类型,可能可以安全地移除一些 typing
导入。在这种情况下,autoflake 会正确删除不再需要的导入。因此,只要配置得当,autoflake 不会破坏类型检查。但为安全起见,建议在运行 autoflake 后检查一下类型注解是否仍然有效,特别是在使用 --remove-unused-variables
时,确保没有删除类型注解中引用的变量。--check
),将其作为警告而非强制错误,以人工确认后再自动清理。总的来说,autoflake 是一个非常有用的辅助工具,与其他代码质量工具配合可以进一步提升代码库的整洁度。通过合理配置和注意上述事项,可以安全地将其纳入开发流程,享受自动清理冗余代码的便利。
pyupgrade 是一个用于自动升级 Python 代码语法的工具。它可以扫描代码并将旧版本 Python 的语法升级为更新版本支持的更简洁或更安全的形式官方仓库。例如,pyupgrade 会将 Python 2 兼容的语法转换为 Python 3 风格,或者将较老的 Python 3.x 语法升级为更新版本的语法。通过使用 pyupgrade,开发者可以快速让代码库符合更高版本 Python 的最佳实践,减少维护旧语法的负担。
核心作用: pyupgrade 的核心功能是代码语法现代化。它能够自动执行许多常见的语法升级操作,包括:
print
语句的括号缺失形式、旧式的八进制字面量(0o
前缀替代 0
前缀)、dict.iteritems()
等 Python 2 特有方法,以及 from __future__
中不再需要的导入(如 unicode_literals
)等。dict()
构造器包裹的列表推导式改为字典推导式,将 set()
构造器包裹的列表推导式改为集合推导式,将旧式的格式化字符串 %s
替换为 str.format()
或 f-string(根据版本)等。typing.List[int]
可简化为 list[int]
)。pyupgrade 可以根据目标版本自动更新这些注解。except Exception, e
旧语法改为 except Exception as e
,将 xrange
替换为 range
(针对仍存在的 Python 2 兼容代码),移除不必要的括号等。通过这些转换,pyupgrade 帮助开发者无痛升级代码库以利用新语言特性,同时减少人工逐一修改的工作量。对于维护多个 Python 版本支持的项目,pyupgrade 也可以根据指定的最低版本自动调整代码,确保代码只包含目标版本支持的语法。
pyupgrade
)# Python 2/3 兼容代码 my_set = set([1, 2, 3]) my_string = "Value is {}".format(42)
pyupgrade --py38-plus
)# 现代 Python 3.8+ 代码 my_set = {1, 2, 3} my_string = f"Value is {42}"
手动使用方式: 安装 pyupgrade:
shellpip install pyupgrade
运行 pyupgrade 升级指定文件或目录。例如:
shellpyupgrade --py310-plus myfile.py # 将文件升级为兼容Python 3.10+的语法 pyupgrade --py39-plus mypackage/ # 递归升级整个包为Python 3.9+兼容
常用参数:
--py310-plus
、--py39-plus
等:指定目标支持的最低 Python 版本。pyupgrade 将根据该版本移除不再需要的兼容代码并应用新语法。例如 --py310-plus
会启用诸如使用 match/case
语法(如果存在旧的 if/elif
链可以转换)、将 isinstance(x, (A, B))
替换为 isinstance(x, A | B)
(联合类型语法)等升级。--keep-percent-format
:保留 %
格式化字符串,不将其升级为 .format()
或 f-string。--keep-mock
:保留 mock
模块的导入(否则 pyupgrade 会将 import mock
替换为 from unittest import mock
)。--exit-zero-even-if-changed
:即使有代码被修改,也返回退出码0(默认情况下,pyupgrade 修改了文件会返回1)。--check
:检查哪些代码会被升级,但不实际修改文件。pyupgrade 的默认行为是直接修改文件。建议在运行前备份代码或通过版本控制管理,以便查看改动。
配置最佳实践: pyupgrade 可以通过命令行参数或配置文件(如 pyproject.toml
的 [tool.pyupgrade]
部分)指定常用选项。以下是一个配置示例:
toml[tool.pyupgrade]
minimum_python_version = [3, 10] # 指定最低支持版本为Python 3.10
keep_percent_format = false # 不保留%格式化,尽量升级为更现代的格式
keep_mock_imports = false # 允许将mock导入替换为from unittest import mock
exit_zero = false # 修改文件时返回非零状态(用于CI检测)
配置说明:
minimum_python_version = [3, 10]
:这是 pyupgrade 最重要的配置项,决定了升级的目标版本。它等同于命令行的 --py310-plus
。设置后,pyupgrade 将应用所有适用于 Python 3.10+ 的升级规则。例如,移除 print
语句(Python 3.10 不再支持)、使用 list
/dict
等泛型类型简写、将旧的异常语法升级等。根据项目实际支持的最低版本,可设置为 [3, 8]
、[3, 9]
等。keep_percent_format = false
:pyupgrade 默认会尝试将 %
格式化字符串升级为 .format()
或 f-string。如果项目中希望保留某些 %
格式化(例如出于性能或习惯原因),可以设置此项为 true
。否则保持 false
,让 pyupgrade 自动升级以提高代码可读性。keep_mock_imports = false
:pyupgrade 默认会将 import mock
替换为 from unittest import mock
,因为标准库在 Python 3.3+ 已包含 unittest.mock
。如果项目仍在使用第三方 mock
库(例如兼容 Python 2),可设为 true
保留原导入。否则设为 false
以符合标准做法。exit_zero = false
:pyupgrade 在修改了文件时默认返回退出码1。这有助于在 CI 中检测到代码需要升级。如果希望即使有修改也返回0(不视为错误),可设为 true
。但通常建议保持 false
,以便 CI 发现代码未升级的情况,提醒开发者提交升级后的代码。通过上述配置,pyupgrade 将按照目标版本自动升级代码。在大型项目中,建议先在 CI 中以检查模式运行 pyupgrade,确认所有修改都是预期的,然后再允许其自动修改文件。此外,pyupgrade 不会处理代码逻辑,仅语法层面的升级,因此升级后应运行测试确保没有引入意外问题。
Pre-commit 集成方式: 在 .pre-commit-config.yaml
中添加 pyupgrade 钩子:
yaml- repo: https://github.com/asottile/pyupgrade
rev: v3.15.0 # 使用稳定版本
hooks:
- id: pyupgrade
args: [--py310-plus] # 指定升级目标版本
这样每次提交前,pyupgrade 都会检查代码并升级语法。如果有代码被修改,pyupgrade 会更新文件并阻止提交,提示重新提交升级后的内容。需要注意的是,pyupgrade 应在代码格式化工具之前运行,因为它可能改变代码结构,随后再由 Black 等格式化可以保证风格一致。例如,在 pre-commit 配置中,pyupgrade 钩子应排在 Black 钩子前面[github.com]。
徽章: pyupgrade 没有官方徽章,但可以在 README 中说明项目使用 pyupgrade 来保持代码符合最新 Python 语法。如果需要,也可以使用 shields.io 生成自定义徽章。
冲突解决: pyupgrade 主要涉及语法层面的修改,与其他工具的交互需要注意以下几点:
与代码格式化工具的顺序: 正如上面提到的,pyupgrade 应在 Black 等格式化工具之前运行。因为 pyupgrade 可能会改变代码的结构(如添加或删除括号、换行等),如果先运行 Black 再运行 pyupgrade,可能导致 Black 已经格式化好的代码被 pyupgrade 修改,从而需要再次运行 Black。因此,在 pre-commit 钩子顺序上,将 pyupgrade 放在前面,Black 放在后面,可以避免这种反复。
与类型检查器的配合: pyupgrade 升级类型注解(如将 List[int]
改为 list[int]
)后,mypy 等类型检查器在对应 Python 版本下能够正确识别这些新语法。但需要确保 mypy 配置的 target-version
与 pyupgrade 的目标版本一致,否则 mypy 可能不认识新语法(例如在 Python 3.8 项目中使用 list[int]
会报错)。因此,升级代码后应同步更新 mypy 等工具的版本配置,以利用新特性并避免误报。
避免过度升级: pyupgrade 非常强大,但也可能“升级”一些本不想改动的地方。例如,它会把所有 %s
格式化升级,包括那些可能出于性能考虑而有意保留的。为了避免此类问题,可以使用配置选项(如 keep_percent_format
)精细控制,或在特定代码行前添加注释 # pyupgrade: ignore
来阻止 pyupgrade 修改该行。例如:
pythonlogger.debug("Performance critical: %s", data) # pyupgrade: ignore
这样 pyupgrade 就不会将这行的 %s
格式化升级。合理使用这些忽略机制,可以在享受自动升级便利的同时保留个别需要的旧语法。
与测试和人工审查: 自动升级后,务必运行项目测试以确保升级没有破坏功能。某些语法升级(如 isinstance(x, (A, B))
改为 isinstance(x, A | B)
)在 Python 3.10+ 是等价的,但在旧版本不兼容,所以只要配置了正确的最低版本,这不会有问题。但如果有疏漏,可能引入错误。因此,pyupgrade 应结合测试和人工审查,作为升级流程的一环,而非完全放任。
总的来说,pyupgrade 是一个非常有用的工具,能够大幅减少升级 Python 版本或清理旧语法的工作量。通过正确配置和与其他工具的配合,可以安全地将其融入开发流程,让代码库始终保持在较新且简洁的语法形式。
pydocstringformatter 是一个用于**自动格式化 Python 文档字符串(docstring)**的工具。它可以检查并重新格式化 docstring,使其符合 PEP 257 等文档字符串风格指南的要求官方仓库。例如,pydocstringformatter 会在多行 docstring 的结尾添加一个空行,统一字符串引号风格,调整缩进,以及移除多余的空白等。通过使用该工具,开发者可以确保项目中的 docstring 格式一致、整洁易读。
pydocstringformatter 的核心功能是规范文档字符串格式。它自动执行以下改进:
通过自动执行这些格式化操作,pydocstringformatter 帮助开发者维护一致的文档风格,减少因为格式不一致而产生的代码审查噪音。同时,格式良好的 docstring 也能被 Sphinx 等文档生成工具更好地解析,生成美观的 API 文档。
安装 pydocstringformatter:
shellpip install pydocstringformatter
运行 pydocstringformatter 格式化指定文件或目录的 docstring:
shellpydocstringformatter --write {source_file_or_directory}
pydocstringformatter 支持通过配置文件(如 pyproject.toml
的 [tool.pydocstringformatter]
部分)设置选项。以下是一个配置示例:
[tool.pydocstringformatter] # 设置为 true,将直接修改文件。如果为 false(默认),则仅打印差异(diff)。 # 在 pre-commit 中通常需要设置为 true。 write = true # 排除不需要格式化的文件或目录(glob 模式) # exclude = ["**/my_dir/**", "**/my_other_dir/**"] strip-whitespaces = true split-summary-body = true # NumPy 风格章节标题下划线长度,false 为不限制 numpydoc-section-hyphen-length = false # 可选:指定要遵循的风格指南。例如,如果项目同时使用 numpydoc 风格,可以添加它。 # 默认为 ["pep257"]。 # style = ["pep257", "numpydoc"]
在 .pre-commit-config.yaml
中添加 pydocstringformatter 钩子:
# .pre-commit-config.yaml - repo: https://github.com/DanielNoord/pydocstringformatter rev: v0.7.3 hooks: - id: pydocstringformatter # 在 pre-commit 中,通常通过 args 来传递 --write 标志 args: ["--write"]
这样每次提交前,pydocstringformatter 都会检查并格式化 docstring。如果有修改,会更新文件并阻止提交,提示重新提交格式化后的内容。
pydocstringformatter 没有官方徽章,但可以在 README 中说明项目使用该工具维护 docstring 格式。
pydocstringformatter 主要关注文档字符串的格式,与其他代码工具的冲突较少。需要注意的是:
D
类错误检查 docstring 风格,那么启用 pydocstringformatter 后,大部分格式问题会被自动修复,从而减少检查时的报错。但需要注意配置的一致性,例如忽略的错误码应在检查工具中也忽略,否则格式化后仍可能有警告。--force
)。如果希望保留 docstring 中的特定结构(如示例代码),可以在配置中排除这些 docstring 或者使用 --force
时谨慎操作。此外,pydocstringformatter 不会修改 docstring 内部的内容语义,只会调整格式,因此无需担心其破坏文档内容。总的来说,pydocstringformatter 是维护 Python 项目文档一致性的有力助手。它与代码格式化和检查工具配合,可以确保代码不仅功能正确、风格统一,连文档都整整齐齐。通过合理配置和集成,开发者可以从繁琐的 docstring 格式调整中解放出来,专注于编写高质量的文档内容。
ruff 是近年来新兴的一款高速 Python 代码分析工具,它将多种传统工具的功能集于一身官方文档。Ruff 最初作为 Flake8 的替代者出现,但现已发展为一个全能型工具,内置了**代码检查(linter)和代码格式化(formatter)**两大功能。它支持超过 1000 条代码规则,几乎涵盖了 Flake8 及其众多插件的检查能力,同时还内置了类似 isort 的导入排序、类似 pydocstyle 的文档字符串检查、类似 pyupgrade 的语法升级等功能。更重要的是,Ruff 由 Rust 编写,运行速度极快,通常比传统 Python 工具快 10-100 倍。
Ruff 的核心作用可以概括为一站式代码质量保障。它通过一个工具提供了以下主要功能:
I001
未排序的导入),并可以自动修复。因此,使用 Ruff 后,通常不再需要单独的 isort 工具。D
开头),可以检查 docstring 是否符合规范,如是否存在、格式是否正确等。开发者可以选择遵循的文档风格(Google、NumPy 等)并在 Ruff 中配置。x == None
改为 x is None
,将 dict.iteritems()
改为 dict.items()
等),以及移除未使用的导入和变量。这些改进很多可以通过 Ruff 的自动修复完成,从而减少人工操作。通过上述丰富的功能,Ruff 旨在成为单一的代码质量工具,替代过去需要多个工具组合才能完成的任务[docs.astral.sh]。这不仅简化了项目配置,也显著提升了运行速度,使开发者可以更频繁地运行检查,从而在早期发现问题。
安装 Ruff:
shellpip install ruff
使用 Ruff 进行代码检查(lint):
shell# 运行 linter
ruff check .
# 自动修复可修复的问题
ruff check . --fix
# 运行 formatter
ruff format .
--fix
参数非常有用,Ruff 会对可修复的问题直接修改文件(类似 black
的行为)。对于检查结果,可以使用 --format
指定输出格式,或使用 --diff
查看修复差异。
使用 Ruff 进行代码格式化:
shellruff format myfile.py # 格式化单个文件 ruff format mypackage/ # 递归格式化整个包
Ruff formatter 会直接修改文件以应用格式更改。可以使用 --check
仅检查不修改,或 --diff
查看格式化差异。
Ruff 还提供了一些便捷的命令,例如 ruff upgrade
可以交互式升级项目的 Ruff 版本和配置,ruff rules
可以列出所有可用规则及其说明等。
配置最佳实践: Ruff 的配置通常放在 pyproject.toml
的 [tool.ruff]
部分(或单独的 .ruff.toml
文件)。由于 Ruff 功能广泛,配置项也较多,但可以根据需要逐步调整。以下是一个推荐的配置示例,涵盖关键选项并附带注释:
toml[tool.ruff]
# 基本配置
line-length = 88 # 代码行最大长度(与Black/Ruff formatter一致)
target-version = "py310" # 目标Python版本,影响可用规则和语法检查
select = [ # 需要启用的规则类别/编号
"A", # 防止关键字覆盖内置名称 (Avoid shadowing builtins)
"B", # bugbear 规则,额外的错误和最佳实践检查
"C", # 逗号风格检查 (Commas)
"D", # pydocstyle 文档字符串规则
"E", # pycodestyle (PEP8) 错误
"F", # pyflakes 错误(未使用变量、导入等)
"I", # isort 导入排序规则
"N", # pep8-naming 命名规范
"Q", # flake8-quotes 引号风格
"T20", #eradicate 规则,检测调试打印语句
"W", # pycodestyle (PEP8) 警告
]
ignore = [ # 需要忽略的具体规则或错误码
"D100", # 忽略:公共模块缺少文档字符串
"D101", # 忽略:公共类缺少文档字符串
"D102", # 忽略:公共方法缺少文档字符串
"D103", # 忽略:公共函数缺少文档字符串
"D104", # 忽略:公共包缺少文档字符串
"D105", # 忽略:魔术方法缺少文档字符串
"D107", # 忽略:类的 __init__ 缺少文档字符串
"E501", # 忽略:行过长(由 formatter 处理)
"W503", # 忽略:行拼接运算符前换行(与 formatter 规则冲突)
]
extend-ignore = [ # 从其他配置继承忽略的规则(如果有)
# "E203", # 例如:忽略冒号前空格(PEP8 E203,Black允许)
]
exclude = [ # 需要排除检查/格式化的文件或目录
"tests/", # 测试目录
"venv/", # 虚拟环境
"build/", # 构建输出
"__pycache__", # Python 缓存目录
]
# Linter 特定配置
[tool.ruff.lint]
fixable = "all" # 允许自动修复所有可修复的问题
unfixable = [] # 明确指定不可自动修复的问题(空表示无)
cache = true # 启用检查缓存以加速后续运行
show-error-codes = true # 输出中显示错误代码
force-exclude = true # 严格排除匹配的文件(即使父目录未被排除)
# 可以针对特定规则设置参数,例如:
# [tool.ruff.lint.pydocstyle]
# convention = "google" # 配置 pydocstyle 使用 Google 风格
# Formatter 特定配置
[tool.ruff.format]
quote-style = "single" # 字符串使用单引号(类似 Black)
indent-width = 4 # 缩进宽度4空格
line-length = 88 # 格式换行长度(应与上面的 line-length 一致)
# 其他格式选项如:
# trailing-comma = "es5" # 类似 Black,对容器字面量添加尾逗号
# wrap-comments = true # 自动换行注释
配置说明:
line-length = 88
:设置代码行最大长度。Ruff 的检查器和格式化器都会遵守此值。建议与 Black 等保持一致,以避免冲突。target-version = "py310"
:指定项目目标支持的最低 Python 版本。这会影响 Ruff 可以应用的规则,例如某些语法检查只在特定版本下有意义。Ruff 会根据此值启用或禁用相应规则。select = [...]}
:列出需要启用的规则类别或具体错误码。Ruff 提供了许多规则分类,如 "A"
代表避免内置名称冲突的规则,"B"
代表 bugbear 规则,"D"
代表文档字符串规则等。通过选择这些类别,可以覆盖广泛的检查范围。如果希望精简检查,可以只选择部分类别。也可以直接指定规则编号(如 "F401"
表示未使用的导入)。ignore = [...]}
:列出要忽略的具体错误码。例如,D100-D105, D107
是文档字符串缺失的错误,如果项目不强制每个公共对象都有 docstring,可以暂时忽略这些。E501
是行过长错误,由于我们使用 formatter 来处理行宽,因此忽略此检查。W503
是关于运算符位置的警告,与 formatter 的风格冲突,也予以忽略。通过 ignore
可以过滤掉不关心或与 formatter 重复的问题。extend-ignore = [...]}
:如果从其他配置文件继承了忽略列表,可以用此扩展,而不会覆盖原有忽略项。exclude = [...]}
:列出要排除的文件或目录模式。这些路径下的文件不会被 Ruff 检查或格式化。例如测试目录、虚拟环境、构建产物目录等通常不需要检查。[tool.ruff.lint]
部分:配置检查器特有的选项。fixable = "all"
表示允许 Ruff 自动修复所有可修复的问题(Ruff 默认也是如此)。cache = true
启用检查缓存,大幅提升重复运行时的速度。show-error-codes = true
让输出显示错误码,方便参考文档。如果需要针对某些规则调整行为,也可以在子部分配置,例如 [tool.ruff.lint.pydocstyle]
下设置文档风格等。[tool.ruff.format]
部分:配置格式化器的选项。quote-style = "single"
指定字符串使用单引号。indent-width = 4
是缩进空格数。line-length
应与顶层的 line-length
一致。Ruff formatter 还有一些高级选项,如 trailing-comma
(控制尾逗号添加策略)、wrap-comments
(自动换行注释)等,可以根据需要启用。通过上述配置,Ruff 将执行严格的代码检查和一致的代码格式化。在实际使用中,可根据项目反馈调整 select
和 ignore
列表,逐步收紧或放宽规则。Ruff 的文档提供了完整的[配置选项列表]和[规则列表],方便查阅每个规则的含义和用途。
在 .pre-commit-config.yaml
中添加 Ruff 钩子。由于 Ruff 同时具备检查和格式化功能,通常需要两个钩子分别处理:
yaml# .pre-commit-config.yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.4
hooks:
# 运行 linter 并自动修复
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
# 运行 formatter
- id: ruff-format
这样每次提交前,Ruff 会先检查并修复代码问题,然后格式化代码。如果有任何改动,提交将被阻止,提示重新提交修改后的内容。需要注意的是,Ruff formatter 本身已经可以处理大部分风格问题,包括导入排序和代码格式化,因此通常不再需要同时使用 isort 或 Black 等工具。如果项目之前依赖这些工具,引入 Ruff 后应移除对应的钩子,以免重复处理。
[](https://github.com/astral-sh/ruff)
Ruff 作为一站式工具,在减少工具数量的同时,也避免了许多传统工具之间的冲突。不过,在与其他工具配合或配置不当时,仍需注意以下几点:
ruff format
来同步格式,之后 Ruff 会保持一致。Ruff 官方也建议在迁移时允许一次格式调整提交,之后就不会有意外的改动。line-length
等在检查和格式化中一致,否则可能出现检查报错但格式化无法解决的情况。此外,如果项目同时使用 Ruff 和 mypy,应注意 Ruff 的某些规则可能与 mypy 有重叠(例如变量未使用,mypy 也可能报告)。但两者侧重点不同:mypy 关注类型,Ruff 关注语法和风格,不会真正冲突。W503
(运算符前换行)和 W504
(运算符后换行)是互斥的,配置时应只启用其中一个(通常 Ruff 会默认处理这种情况)。如果遇到误报,可以通过 ignore
忽略特定规则或使用 # noqa: CODE
注释忽略某行。Ruff 社区活跃,规则也在不断改进,遇到问题可以查阅文档或提交反馈。总的来说,使用 Ruff 可以极大简化 Python 项目的工具链,并显著提高代码检查和格式化的效率。通过正确的配置和替换旧工具,Ruff 能够与项目现有流程无缝衔接,带来更快的反馈循环和更一致的代码质量。对于追求高效和简洁的团队来说,Ruff 是一个值得尝试的现代代码质量工具。
将所有工具整合在一起时,执行顺序至关重要。一个合理的顺序可以避免工具间的相互干扰,例如,避免 linter 报告一个即将被 formatter 修复的问题。下面提供一个经过深思熟虑的完整配置方案。
基本原则是:先清理和升级,再格式化,最后进行质量与安全检查。
pre-commit-hooks
): 首先运行一些与语言无关的基础检查,如修复文件末尾空行、检查大的二进制文件等。pyupgrade
): 在格式化之前升级语法,因为新语法可能有不同的最佳格式。autoflake
): 移除未使用的导入和变量,避免后续工具对这些冗余代码进行格式化或检查。isort
): 在主格式化工具之前整理 imports。black
, pydocstringformatter
): 应用统一的代码风格。bandit
): 在代码结构稳定后进行安全扫描。mypy
): 作为最耗时、最深入的检查之一,放在最后,确保在代码格式和内容都确定后进行。如果使用 ruff
,顺序会大大简化:pre-commit-hooks
-> ruff
(lint & format) -> bandit
-> mypy
。
.pre-commit-config.yaml
示例以下是一个包含所有介绍工具(非 ruff
版本)的完整配置文件,并展示了如何集成一个本地脚本。
# .pre-commit-config.yaml # 参见:https://pre-commit.com/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 hooks: - id: trailing-whitespace # 移除行尾多余的空格 - id: end-of-file-fixer # 确保文件以单个空行结尾 - id: check-yaml # 检查 YAML 文件语法 - id: check-added-large-files # 防止大文件被意外提交 - repo: https://github.com/asottile/pyupgrade rev: v3.16.0 hooks: - id: pyupgrade args: [--py38-plus] - repo: https://github.com/PyCQA/autoflake rev: v2.3.1 hooks: - id: autoflake args: - --in-place - --remove-all-unused-imports - --remove-unused-variables - repo: https://github.com/pycqa/isort rev: 5.13.2 hooks: - id: isort name: isort (python) - repo: https://github.com/psf/black rev: 24.4.2 hooks: - id: black - repo: https://github.com/DanielNoord/pydocstringformatter rev: v0.7.3 hooks: - id: pydocstringformatter args: ["--write"] - repo: https://github.com/PyCQA/bandit rev: 1.7.9 hooks: - id: bandit args: ["-c", "pyproject.toml"] additional_dependencies: ["bandit[toml]"] - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.10.0 hooks: - id: mypy additional_dependencies: [types-requests] # 示例:如何集成一个本地脚本作为 hook - repo: local hooks: - id: custom-check name: Custom local check entry: python scripts/my_custom_check.py language: python types: [python] # 假设 scripts/my_custom_check.py 是一个自定义检查脚本
通过本文的探讨,我们构建了一个全面的 Python 代码标准化工程体系。然而,如何将这些工具最高效地融入日常开发流程,是实现其价值的最后一步。这需要区分不同工具的最佳应用场景。
实时检查 (On-the-fly in IDE):
这一阶段的目标是在编写代码的瞬间就获得反馈,避免错误累积。适合此阶段的工具应具备极高的响应速度和良好的编辑器集成。
首选工具: Ruff
(通过其 VSCode 扩展) 或 Pylance。它们能提供即时的错误高亮、格式化、导入排序和代码建议,将质量控制前置到编码的每一秒,极大地提升了开发效率和体验。
提交前检查 (Pre-commit Stage):
这是代码进入版本库前的最后一道质量门禁,其核心目标是“防错”而非“纠错”。此阶段的检查必须是强制性的、全面的。
首选工具: 所有工具都应在此阶段通过 pre-commit
框架进行配置。这确保了即使开发者的 IDE 配置不当或被暂时禁用,所有代码在合入团队仓库前也必须通过统一的、严格的标准化流程。它是保障团队代码质量下限的基石。
black
和 isort
开始,首先统一代码风格,然后再逐步引入 autoflake
, pyupgrade
, mypy
和 bandit
。ruff
,简化未来: 对于新项目或愿意进行迁移的团队,可以尝试使用 ruff
。它不仅能带来显著的性能提升,更能极大简化工具链的管理和配置,是 Python 代码质量工具的未来趋势。pyproject.toml
和 .pre-commit-config.yaml
文件视为项目的重要技术文档。清晰的配置、详尽的注释本身就是一种团队规范的沉淀和共识的体现。本文作者:Silon汐冷
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!