Python 项目里常见一串:isort 排 import、black 排版、flake8/ruff 查问题。它们各管什么、放在一起会不会打架、ruff 能不能一个顶三个,这篇用同一段乱代码全部真跑了一遍,包括那个著名的「isort 与 black 互相不认」冲突。
messy.py(节选) import sys, os from typing import List,Dict, Optional import requests_not_installed from collections import (OrderedDict, defaultdict, Counter, deque, namedtuple, ChainMap, UserDict) import json import re def build(items:List[int], mapping: Dict[str,int]={}, flag = True)->Optional[str]: result = {'a':1,'b':2, 'c':3} ...
版本 版本: black 26.5.1; isort 9.0.2; ruff 0.16.9
black 改动 31 行 | 其中涉及 import 的 4 行 | 是否把 {'a':1 ...} 改成 {"a": 1, ...}: True
isort 改动 14 行 | 其中涉及 import 的 11 行 | 是否把 {'a':1 ...} 改成 {"a": 1, ...}: False
ruff format 改动 31 行 | 其中涉及 import 的 4 行 | 是否把 {'a':1 ...} 改成 {"a": 1, ...}: True
ruff check --select I --fix 改动 21 行 | 其中涉及 import 的 11 行 | 是否把 {'a':1 ...} 改成 {"a": 1, ...}: False
→ black 与 ruff format 是「代码格式」,isort 与 ruff -I 是「import 排序」。谁也不会删掉没用到的 import:
messy.py:1:8: F401 [*] `sys` imported but unused
messy.py:1:13: F401 [*] `os` imported but unused
messy.py:3:8: F401 [*] `requests_not_installed` imported but unused
messy.py:4:26: F401 [*] `collections.OrderedDict` imported but unused
messy.py:5:5: F401 [*] `collections.defaultdict` imported but unused
messy.py:5:18: F401 [*] `collections.Counter` imported but unused
messy.py:5:27: F401 [*] `collections.deque` imported but unused
messy.py:5:34: F401 [*] `collections.namedtuple` imported but unusedblack 与 ruff format 的改动几乎相同(31 行,其中动到 import 的只有 4 行——只是排版);isort 与 ruff 的 I 规则只动 import(isort 改 11 行 import,代码本体不动)。没有一个会删除没用到的 import:sys、os、requests_not_installed 等全都还在,想找出它们要靠 lint(ruff 的 F401,上面列出了前几条,可以加 --fix 自动删)。
待处理的 import 片段:
import sys, os
from typing import List,Dict, Optional
import requests_not_installed
from collections import (OrderedDict,
defaultdict, Counter, deque, namedtuple, ChainMap, UserDict)
import json
import re
isort(默认配置) 之后, black --check 返回码 = 1 (1 = black 还想再改)
isort 默认生成:
import json
import os
import re
import sys
from collections import (ChainMap, Counter, OrderedDict, UserDict, defaultdict,
deque, namedtuple)
from typing import Dict, List, Optional
import requests_not_installed
black 想改成:
+from collections import (
+ ChainMap,
+ Counter,
+ OrderedDict,
+ UserDict,
+ defaultdict,
+ deque,
+ namedtuple,
+)
isort --profile black 之后, black --check 返回码 = 0 (0 = 已经一致)isort 默认把过长的 import 排成「网格」风格:续行缩进到括号位置;black 的风格是「一行一个名字 + 尾逗号」。所以先用默认配置的 isort,black 再检查一定说「还没格式化」。--profile black 让 isort 直接输出 black 风格,冲突消失(返回码 0)。
isort(默认) → black black --check: 通过 | isort --check(默认配置): 失败 | isort --check(black profile): 通过 black → isort(默认) black --check: 失败 | isort --check(默认配置): 通过 | isort --check(black profile): 失败 isort --profile black → black black --check: 通过 | isort --check(默认配置): 失败 | isort --check(black profile): 通过 black → isort --profile black black --check: 通过 | isort --check(默认配置): 失败 | isort --check(black profile): 通过
四种流水线的最终状态:①isort 用默认配置时,无论先后,总有一个检查会失败:isort 在前则 isort --check 用默认配置时失败,isort 在后则 black --check 失败;②isort 用 --profile black,先后都能让 black 和 isort(black profile)两个检查通过。注意这里只是同一份样例的结果,配置项更多的项目里可能有别的冲突。
isort(black profile)+black 与 ruff(I)+ruff format 的结果完全相同? True
最终代码:
import json
import os
import re
import sys
from collections import (
ChainMap,
Counter,
OrderedDict,
UserDict,
defaultdict,
deque,
namedtuple,
)
from typing import Dict, List, Optional
import requests_not_installed
def build(items: List[int], mapping: Dict[str, int] = {}, flag=True) -> Optional[str]:
result = {"a": 1, "b": 2, "c": 3}
long_call = some_function_name(
argument_number_one,
argument_number_two,
argument_number_three,
argument_number_four,
)
if flag:
return "yes"
else:
return "no"在这份样例上,ruff check --select I --fix 加 ruff format 的结果和 isort --profile black 加 black 的输出完全相同(比较结果 True,最终代码见上)。这只说明这份样例上一致,不是所有代码都一致——两者的实现是独立的,遇到边缘情况(比如特殊注释、超长字符串)可能有差异,这里没有验证。
black 918 ms ruff format 24 ms isort --profile black 229 ms ruff check --select I --fix 26 ms (共 10200 行, 300 个文件; 每次都从原始文件重新拷贝, 取 3 次最小值)
300 个文件(共 10200 行):black 要 918 ms,ruff format 24 ms;isort 229 ms,ruff 的 import 排序 26 ms。差距在 CI 或者保存文件自动格式化时最容易感觉到。速度不是选工具的唯一理由,但配置只需要维护一份是实打实的好处。