Python graphlib 依赖排序:字典里要写前置任务,完成后才能放行下一步
先把“依赖谁”写清楚
制作一份报告时,抓取资料后才能校验,校验通过后才能计算图表与排版,最后才能打包。若只是按函数在文件里的位置执行,流程一长就难以看清先后关系。标准库 graphlib 可以把这些约束转成可执行顺序,让依赖关系成为可检查的数据。它解决顺序条件,任务怎样运行仍由程序安排。
最容易写反的是字典方向。键表示当前任务,值表示它的全部前置任务。因此 package 对应 charts 和 pages,含义是打包要等这两项完成。前置节点即使没有单独作为键出现,也会被自动加入;正式配置最好把节点写全,并额外核对名称,免得拼写错误被当成新任务。
把就绪与完成分成两步
下面的独立脚本在 Python 3.12.14 上运行,使用的 graphlib 从 Python 3.9 起提供。prepare 先固定图并检查循环;get_ready 返回当前已经满足前置条件的一批节点;done 则报告哪些节点真正完成。这个接口允许把一批节点派给多个工作者,但并不替你启动线程或进程。
实验将同一批就绪节点排序后打印,只是为了让展示稳定。charts 与 pages 之间没有依赖,谁先执行都符合图的约束。不能把拓扑排序的一次输出误读为唯一顺序,也不要为了让输出好看而给独立任务随便添加依赖,那会降低本可利用的并行度。
AI概念示意图:有向依赖从一个入口分成两路再汇合,下方独立圆环表示循环依赖。图片用于解释概念,不是运行截图。
from graphlib import TopologicalSorter, CycleError
graph = {
"fetch": (),
"check": ("fetch",),
"charts": ("check",),
"pages": ("check",),
"package": ("charts", "pages"),
}
ts = TopologicalSorter(graph)
ts.prepare()
finished = set()
batches = []
while ts.is_active():
ready = sorted(ts.get_ready())
assert ready
assert ts.get_ready() == ()
for node in ready:
assert set(graph[node]) <= finished
finished.add(node) # 本例用断言模拟成功完成
batches.append(ready)
ts.done(*ready)
print(batches)
assert batches == [["fetch"], ["check"],
["charts", "pages"], ["package"]]
try:
TopologicalSorter({"draft": ("review",),
"review": ("draft",)}).prepare()
except CycleError as exc:
cycle = exc.args[1]
assert cycle[0] == cycle[-1]
assert set(cycle) == {"draft", "review"}
print("cycle detected:", sorted(set(cycle)))
else:
raise AssertionError("expected a cycle")读懂一轮轮放行的结果
输出共有四批:fetch、check、并列的 charts 与 pages、最后的 package。第二次立刻调用 get_ready 得到空元组,因为第一批节点已经交给调用方,排序器不会把它们重复发放。它们尚未 done 时,依赖它们的节点也不会提前出现。这个细节正好能检查调度器是否把“拿到任务”混同于“任务完成”。
真实任务失败后,应由业务决定重试、终止或记录失败。不要为了让循环继续而对失败节点调用 done,否则后续任务会把失败输入当成已就绪。本例所有工作同步完成,所以每轮必有可执行项;异步执行时 get_ready 可能暂时为空,但仍有已派发任务正在运行,需要等待完成事件,不能直接判定死锁。
循环不是换个顺序就能解决
第二张图让初稿等待审阅、审阅又等待初稿,prepare 会抛出 CycleError。异常携带一条首尾相同的循环路径;若图中有多条循环,它不保证列出全部。输出把涉及的节点排序,是为了验证这个小例子的成员,而不是声称异常总按某个固定方向返回。
修复时要回到真实流程,确认审阅究竟需要初稿还是更早的提纲,再调整依赖。对于只需要一次线性顺序的小脚本,也可使用 static_order;需要跟踪并行任务完成状态时,再用本例的分阶段接口。依赖图应描述必要条件,资源数量、重试次数和超时仍要另外设计。把实际完成事件与节点名称一一对应,才能避免误放行。


