# 深入理解 Python isort:一个不太起眼但非常有用的工具
写 Python 代码的时候,我们每天都在跟 import 语句打交道。看起来就是把需要的模块引进来,但实际上,import 的排列方式有时候会让人很头疼。特别是团队协作的时候,每个人写 import 的习惯都不一样,有人喜欢按字母排序,有人喜欢把标准库和第三方库分开,还有人喜欢把相对导入和绝对导入混在一起。时间久了,代码仓库里的 import 部分就变得乱七八糟的。
这里想聊一个专门解决这个问题的工具 —— isort。它不像 Flask、Django 那样名声在外,但在日常开发中,它解决的问题其实非常实在。
isort 是什么
isort 的全称是 “Import Sorter”(导入排序器)。从名字就能看出来,它的核心功能就是整理 Python 文件中的 import 语句。但如果你以为它只是简单的排个序,那就太小看它了。
这个工具其实是一个 Python 实用工具,它可以自动对 import 进行排序、分组和格式化。它遵循的是 PEP8 的建议 —— 把 import 分成三个部分:标准库、第三方库、本地库,并且每组之间用空行隔开。但 isort 做的事情比 PEP8 建议的更加细致。它还支持多种配置方式,可以适应不同项目或团队的编码规范。
有意思的是,isort 的开发者 Timothy Crosley 在 2013 年写了这个工具,原本只是自己用,后来发现很多人都遇到同样的困扰,就把它开源了。现在 isort 已经成为很多 Python 项目的标配工具。
它能做什么
先说说 isort 能解决哪些实际问题。
假设你在一个 Django 项目里工作,代码中有几十个 import 语句,有的来自 Django 本身,有的是自己写的 models,还有几个是 Python 内置的 os、sys 之类的。手动整理这些 import,谁都不愿意干这个活。isort 可以在几毫秒内完成这个工作,而且比手动更准确。
isort 还能处理一些更复杂的场景。比如相对导入和绝对导入的处理,有些项目要求统一使用绝对导入,有些则允许相对导入。isort 可以根据配置决定怎么处理这些情况。再比如有些项目要求 import 不能跨行,有些项目则允许长 import 拆成多行。isort 都能应对。
还有一个很实用的功能是自动检测并修复重复的 import。有时候开发过程中不小心写了两次相同的 import,isort 会自动合并它们。虽然看起来是个小功能,但在大型项目中能减少很多潜在的错误。
isort 还可以和 pre-commit 或者 CI/CD 流程集成。也就是说,你可以在提交代码之前自动运行 isort,或者在 CI 流水线上检查 import 的格式是否正确。这样就能保证整个团队的代码风格保持一致。
怎么使用
安装 isort 很简单,就用 pip:
pipinstallisort最基本的用法就是直接在终端运行:
isort your_file.py这样会自动对指定的 Python 文件进行 import 排序。如果想一次处理多个文件或者整个目录:
isort.# 处理当前目录下所有 Python 文件isort src/# 只处理 src 目录但更常见的做法是在项目里配置好规则,然后一键处理。配置文件可以有几种形式:在项目根目录放一个.isort.cfg文件,或者在pyproject.toml、setup.cfg里添加 isort 的配置。
比如一个简单的.isort.cfg文件:
[settings] profile = black line_length = 88 known_third_party = django, requests known_first_party = myproject这里profile = black是告诉 isort 兼容 black 的格式化风格。用过 black 的朋友应该知道,black 对 import 的排列有它自己的规则,而 isort 可以遵循这些规则。known_third_party和known_first_party是告诉 isort 哪些库是第三方、哪些是本地的,这样它才能正确分组。
检查模式下可以用--check-only或者-c:
isort --check-only src/这样只检查 import 的格式是否正确,不会真正修改文件。很适合在 CI 中使用。
还有个很好用的功能是--diff参数,它可以展示 isort 将会做哪些修改,但实际不修改文件。这样在提交之前可以预览一下效果。
最佳实践
在实际项目中使用 isort,有几点值得注意。
第一个是 pre-commit 集成。把 isort 加入到 pre-commit hooks 中,这样每次提交代码前都会自动检查并排序 import。一个典型的.pre-commit-config.yaml配置如下:
repos:-repo:https://github.com/PyCQA/isortrev:5.13.2hooks:-id:isortargs:["--profile","black"]这样提交代码时 isort 就会自动运行,不用自己操心。
第二个是兼容性。isort 很流行的原因是它几乎可以和所有主流格式化工具配合使用,比如 black、flake8、pylint。但要注意的是 isort 5.x 版本之后有一些 breaking changes,特别是在配置方面。升级的时候需要确认配置文件是否还兼容。
第三个是关于分组的细粒度控制。有些项目对 import 的分组有特殊要求,比如把 Django 相关的放在一起,DRF 的相关放在另一组。isort 支持自定义分组,可以通过known_third_party、sections等配置来实现。还可以设置force_single_line强制每行只有一个 import,或者include_tail控制 import 后面是否加空行。
第四个是处理init.py 文件。init 文件里的 import 有时候比较特殊,它可能包含相对导入、别名导入,或者只是为了导出某些模块而写的from . import形式。isort 默认会处理这些情况,但可以考虑给 init 文件单独设置配置,比如使用skip __init__ = True跳过某些 init 文件的检查。
同类技术对比
说到 import 格式化工具,目前常见的有 isort、reorder-python-imports 和 autoflake 中的 import 排序功能。
reorder-python-imports 是 Google 出的一个工具,功能比 isort 轻量一些。它主要做的是重新排序 import,但分组的逻辑相对简单。它有一个有趣的功能是自动移除不再使用的 import(通过分析 AST),这一点 isort 做不到。不过 reorder-python-imports 的配置方式比较原始,需要手动指定哪些是第三方库,而且它对相对导入的处理不如 isort 那么灵活。
autoflake 是一个更全能的工具,除了 import 排序,它还能移除未使用的变量、移除未被引用的 import(通过检测)。但 autoflake 的 import 排序功能比较基础,不像 isort 那样有丰富的配置选项。
还有一个是 PyCQA 的 flake8-import-order,它是 flake8 的一个插件,只做检查不做修复。如果你的团队只用 flake8 做代码检查,这个插件是很好的选择。但它不能自动排序,需要人工修正。
有没有更好的选择?其实现在很多人把 isort 和 black 配合使用。isort 处理 import 部分,black 负责其他代码格式化。两者配合得比较好,isort 提供了--profile black来保证兼容 black 的风格。当然,如果你用的是类似于 Ruff 这样的新一代 linting 工具,它内置了 import 排序的功能(虽然是模仿 isort 实现的),但作为独立工具,isort 仍然有它的价值。
最后想说的
isort 看起来只是个辅助工具,但在团队协作中,它解决了一个很实际的问题 —— 代码风格的统一。花时间去配置好 isort,并且在 CI 中强制检查,长远来看能节省很多精力。毕竟谁也不想在 code review # # 关于Python Ruff的一些体会
它到底是什么
说实话,我第一次接触Ruff的时候,完全没当回事。一个用Rust写的Python linter?这年头用Rust重构的工具多了去了,fast又怎样,能用才是硬道理。
但用了一段时间后,我发现自己每天都在用它。不是那种“啊我得记得跑一下lint”的用法,而是它已经变成了肌肉记忆——保存文件的同时,Ruff就已经把代码整理得干干净净了。
Ruff本质上是一个Python的代码检查工具,它同时集成了linter和formatter的功能。你可以把它理解为用Rust重新实现了一遍Flake8加上isort,再加上一些额外的检查规则。但这么说其实有点不公平,因为它的速度确实让人上瘾——处理一个中型Django项目,Flake8可能要跑个两三秒,Ruff大概在几十毫秒内搞定。
它能做些什么
说几个最直观的场景。
代码风格检查。这个是最基本的。比如明明用了== None而不是is None,或者某行代码超过了自己设定的长度限制,或者import语句的顺序不对——这些Ruff都能在按下保存键的瞬间帮你指出来。
自动格式化。这个功能挺有意思。Ruff的格式化器其实是在对标Black,但它的配置选项比Black灵活。比如说,我个人的偏好是单引号,这在Black里得用额外配置,而且经常会和别的工具冲突。Ruff就简单得多,规则定义清晰,想改什么直接改配置就行。
import排序。这个我觉得特别实用。写过稍微大一点的Python项目的人都有这种体验:import语句越堆越多,最后变成一个乱七八糟的大杂烩。Ruff会自动把它分成标准库、第三方库、本地模块三个部分,还能按字母排序。这玩意看起来是个小功能,但真的能让代码干净不少。
一些安全检查。比如检测出代码中使用了eval(),或者存在SQL注入的风险。虽然它比不上Bandit那样专业的安全检测工具,但对于日常开发来说已经够用了。
怎么用起来
安装简单得过分:
pipinstallruff然后你可以在项目里创建一个pyproject.toml,写上一些配置:
[tool.ruff] # 选择你想用的规则 select = ["E", "F", "I", "N"] # 忽略某些规则 ignore = ["E501"] [tool.ruff.format] # 使用单引号 quote-style = "single"实际上,你甚至什么都不配置也能直接用。默认的规则集已经覆盖了大部分常见的代码问题。
最常用的命令大概就这么几个:
# 检查代码问题ruff check.# 自动修复能修复的问题ruff check--fix.# 格式化代码ruffformat.如果你用的是VS Code,装个Ruff的扩展就能在保存时自动格式化了。我个人的习惯是结合pre-commit使用,在提交前自动跑一遍检查,这样能确保提交到仓库的代码都是干净的。
一些用得上的经验
说说我踩过的一些坑。
规则选择要克制。Ruff支持超过700条规则,但这不意味着全都用上。我的经验是,从默认规则集开始,遇到真正困扰你的问题再额外加规则。Python社区的一大特色就是代码可读性强,不要在lint规则上搞得太教条主义。
和Black的区别。如果项目已经在用Black,要切换成Ruff的格式化器时,记得检查一下差异。两者在大方向上基本一致,但在一些细节处理上不同。比如Black处理长字符串的方式就和Ruff有点区别。一般来说,我个人觉得除非是新建项目,否则没必要特意从Black迁移到Ruff。
性能优势的真正用途。Ruff快不是最大的卖点,最大的卖点是:因为快,所以你可以在pre-commit钩子里一次性跑更多的检查,而不会影响开发体验。我之前在某个项目里配置了20多个检查规则,加上自动修复和格式化,整个流程跑完也就一两百毫秒。换成Flake8加Black的组合,同样的配置得等上四五秒。这个差异在“每次提交都要等待”的场景下,体验差距是巨大的。
lint和format要区分开。Ruff把这两个功能分开是有道理的。ruff check负责捕捉代码问题和潜在bug,ruff format只处理格式。不要混淆它们的功能。有些问题比如未使用的变量,format是处理不了的。
和其他工具的对比
说几个我实际用过或者评估过的替代方案。
Flake8是我之前的主力工具。它的优点是生态好,插件多,几乎你能想到的任何代码检查场景都能找到对应的插件。缺点是慢,真的很慢。而且Flake8对pyproject.toml的支持不太好,得用独立的配置文件。Ruff几乎全面覆盖了Flake8的功能,速度还快了几十上百倍。
Black作为格式化器,在Python社区的地位挺高的。它的“不妥协”哲学对团队协作确实有帮助——反正大家都用同样的格式,不用争论要不要加空格这种事。但问题也在这里:你不喜欢Black的某些风格也无能为力。Ruff的格式化器在灵活性和一致性之间找到了更好的平衡点。
Pylint比Flake8更严格,能检查出更多潜在问题。但它的误报率也挺高的,而且配置复杂到让人头疼。Ruff可以做到Pylint 80%左右的覆盖,但配置简单得多。
isort专门处理import排序。Ruff直接把这个功能合并进来了。如果不是对import排序有特别苛刻的要求,完全可以用Ruff替代。
说到底,Ruff最大的优势不是某个单点功能有多强,而是它把手伸到了代码检查的每个环节,并且每个环节都做得还算不错。对于一个Python项目来说,装上Ruff,基本上就解决了代码风格和基础质量的问题。的时候因为 import 的顺序问题浪费时间。
如果你的项目还没有使用 isort,不妨试试看。它很小众,但确实是个靠谱的工具。而且一旦习惯了他的工作方式,回头看那些手动整理的 import 代码,你会发现以前的做法有多不划算。