doit:用 Python 写任务流水线,比 Makefile 更顺手
doit:用 Python 写任务流水线,比 Makefile 更顺手
做项目久了,总会遇到这种场景:编译、测试、打包、部署,步骤越来越多,Makefile 写得越来越复杂,最后变成一坨难以维护的字符串拼接。doit 这个项目走的完全是另一条路,它把任务自动化这套逻辑搬回了 Python 生态,让你用写函数的方式定义流水线。目前在 GitHub 上拿到了 2,063 个 Star,不算爆款,但用的人评价都很实在。

核心思路:任务即函数
doit 的设定很直白。你创建一个 dodo.py,在里面写普通的 Python 函数,每个函数返回一个字典,描述这个任务要做什么、依赖哪些文件、生成哪些目标文件。框架自己负责判断哪些任务需要执行,哪些可以跳过。
举个最简单的例子。定义两个任务:第一个生成一个文本文件,第二个读取这个文件并转换成大写版本。代码长这样:
def task_hello():
return {
'actions': ['echo "Hello from doit" > hello.txt'],
'targets': ['hello.txt'],
'clean': True,
}
def task_shout():
return {
'actions': ['tr a-z A-Z < hello.txt > shout.txt'],
'file_dep': ['hello.txt'],
'targets': ['shout.txt'],
'clean': True,
}
安装后直接运行 doit,它会按依赖顺序执行 hello 和 shout。再次运行时,由于目标文件已经存在且依赖没有变化,doit 会直接跳过。需要清理就用 doit clean,生成的文件会被删除。
这套机制看起来简单,但背后做了不少事情。
实际能用上的能力
首先是增量构建。doit 会追踪每个任务的文件依赖和目标文件的时间戳,只有依赖发生变更时才会重新执行。对于大型项目,这能省下大量重复编译或数据处理的时间。
其次是 DAG 调度。任务之间的依赖关系会被解析成有向无环图,执行顺序自动确定,不会出现循环依赖导致的死锁。
并行执行也内置了。独立的任务可以并发跑,支持多进程或多线程模式。CPU 密集型的数据处理场景下,这个特性很实用。
子任务生成是另一个亮点。一个函数可以通过 yield 返回多个任务字典,适合批量处理。比如你有几十个文件要转换,写一条规则就能全部覆盖,不用手动枚举。
计算依赖 calc_dep 允许动态生成依赖图。某些任务的依赖关系在运行前无法确定,doit 支持在任务执行过程中计算并注册新的依赖,这在处理复杂构建流程时很有价值。
插件体系也做得比较开放。命令、报告器、后端存储、任务加载器都可以自定义扩展,不会被框架本身的实现限制住。

和同类工具比怎么样
Makefile 的问题是语法老旧,跨平台兼容性差,写复杂逻辑时很痛苦。各种 YAML 配置的任务工具虽然视觉上清爽,但遇到需要动态生成任务或条件判断的场景,往往要绕很多弯路。
doit 的定位介于两者之间。它比 Makefile 更易读、更可维护,又比纯配置型工具更灵活。因为任务本身就是 Python 代码,你可以在任务定义里写循环、条件判断、调用任意第三方库,不需要额外学习一套 DSL。
当然,它也有局限。doit 的主要使用场景是文件级别的构建和数据处理流水线,如果你的需求是分布式任务调度或定时任务管理,它并不是为此设计的。
适合谁用
如果你在用 Python 做数据分析、模型训练、文档生成,或者需要维护一套中等复杂度的构建流程,doit 值得试试。它不要求你改写现有代码,只需要在一个文件里定义好任务规则,就能获得增量构建、自动依赖管理、并行执行这些能力。
项目采用 MIT 协议,可以直接商用或二次开发。维护者 Eduardo Naufel Schettino 从 2008 年就开始维护这个项目,文档和社区讨论都比较活跃。
对于已经厌倦 Makefile 又不想让构建系统变得过于笨重的开发者来说,doit 提供了一个相当务实的选择。
比较活跃。
对于已经厌倦 Makefile 又不想让构建系统变得过于笨重的开发者来说,doit 提供了一个相当务实的选择。
更多推荐
所有评论(0)