规则(rules)基础归纳总结
上一章我们搭好了 Snakefile 的架子,也跑通了最基础的 hello_world 规则。但你肯定觉得——这离实际干活还差得远。没错,规则才是 Snakemake 的灵魂。这一节,我把规则的核心要素串一遍,顺便聊聊我平时踩过的坑。
规则是什么
说白了,规则就是告诉 Snakemake 三件事:要什么、拿什么换、怎么换。你去餐厅点餐,菜单上的菜名就是规则名,你想吃的成品是输出,厨房里的原料是输入,厨师的操作就是 shell 或 run 块。
Snakemake 通过规则之间的输入输出依赖自动建图。它会先扫描所有规则,然后根据你要的最终文件,反向推导出需要执行哪些规则、按什么顺序。
规则的核心结构
一个完整规则通常包含这几个字段。我列了个表,方便你对照着看:
| 字段 | 必/选 | 作用 | 常见写法 |
|---|---|---|---|
rule 名称 | 必 | 全局唯一标识,供 snakemake 命令行引用 | rule admet_predict: |
input | 选 | 声明依赖的上游文件 | input: "data/raw.sdf" |
output | 必(通常) | 声明本规则生成的文件 | output: "results/{sample}.csv" |
shell / run | 二选一 | shell 给 shell 命令,run 给 Python 代码块 | shell: "obabel {input} -O {output}" |
log | 选 | 重定向标准输出/错误,便于调试 | log: "logs/{sample}.log" |
threads | 选 | 声明规则需要的线程数 | threads: 4 |
resources | 选 | 自定义资源,如内存、GPU | resources: mem_mb=2000 |
params | 选 | 向 shell/run 传递额外参数 | params: cutoff=0.5 |
wildcard_constraints | 选 | 限制通配符匹配模式,防止误匹配 | wildcard_constraints: sample="[a-z]+" |
看到这表你可能会问:input 和 output 里的花括号是什么?嗯,这就是通配符,Snakemake 最强大的特性之一。
输入输出与通配符
通配符让规则能批量处理文件。比如你有一百个小分子文件,不用写一百条规则,只写一条带通配符的规则就够了。
rule admet_predict:
input:
"data/{sample}.sdf"
output:
"results/{sample}_admet.csv"
shell:
"my_admet_tool --input {input} --output {output} --cutoff 0.3"
Snakemake 会自动将 {sample} 替换成实际的文件名基干。当你请求 "results/compound01_admet.csv" 时,它就知道需要 "data/compound01.sdf" 作为输入。
我的经验:写通配符名称时最好用有意义的单词,比如 sample、mol_id、batch,别用a、b。有一次我用了 x 和 y,三天后再看代码完全不知道自己想表达什么。
规则进阶技巧
1. 使用 log 来捕获日志
我曾经在集群上跑一个 ADMET 预测,跑了 8 小时突然失败,却不知道失败原因——因为标准输出全被吞掉了。从那以后,我养成了给每个规则加 log 的习惯:
rule admet_predict:
input:
"data/{sample}.sdf"
output:
"results/{sample}_admet.csv"
log:
"logs/{sample}.log"
shell:
"my_admet_tool --input {input} --output {output} > {log} 2>&1"
注意这里用 > {log} 2>&1 把 stdout 和 stderr 一起重定向到日志文件。如果出错,直接去 logs 目录看最后一屏就行了,不用重新跑。
2. params 传参更灵活
有时我们需要按规则调整参数,比如不同分子用不同的 cutoff。用 params 可以从外部文件或函数里取值:
rule admet_predict:
input:
"data/{sample}.sdf"
output:
"results/{sample}_admet.csv"
params:
cutoff=lambda wildcards: get_cutoff(wildcards.sample)
shell:
"my_admet_tool --input {input} --output {output} --cutoff {params.cutoff}"
lambda wildcards 是 Snakemake 的惯用写法,通配符对象会传进去,你可以根据样本名动态决定参数。
3. 资源声明防止集群被撑爆
在 HPC 或云上,不声明资源会导致所有任务一起跑,把节点内存打满。我个人习惯至少声明 threads 和 mem_mb:
rule admet_predict:
input:
"data/{sample}.sdf"
output:
"results/{sample}_admet.csv"
threads: 4
resources:
mem_mb=2000,
time_min=30
shell:
"my_admet_tool --input {input} --output {output} --threads {threads}"
常见陷阱与最佳实践
- 忘了写 output:Snakemake 允许没有
output的规则(比如最后的汇总),但大部分规则必须有输出,否则工作流无法串联。我之前写过一个数据下载规则没写 output,结果每次运行都重新下载,相当尴尬。 - 通配符命名冲突:不要把
{sample}和{sample_id}混在同一个规则的不同文件里,Snakemake 会把它俩当成不同的通配符,导致无法匹配。 - shell 里用 Python 变量没大括号:
shell块里引用 Python 变量要用{variable},不是${variable}。这是新手最容易犯的错,因为 shell 语法习惯性加$。 - 多余的空格:输入输出列表里,相邻文件之间不要留空行,否则 Snakemake 会认为规则定义结束了。
避坑指南:我曾经在 shell 块里写了一个多行命令,每行末尾用了反斜杠续行,结果 Snakemake 把续行解析成了多条命令。正确做法是用 shell: 后面只放一行命令,或者用 run: 块配合 subprocess 来执行复杂脚本。
规则结构一目了然
下面这个图把规则的核心要素画了出来,方便你日后写规则时对照。
图里把常见的字段都标出来了。你写规则的时候,心里就照着这个模板搭:先把 input/output 想清楚,再决定用 shell 还是 run,最后根据需要补上 log、params、resources。只要结构不乱,流水线就能稳稳地跑起来。