规则(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选自定义资源,如内存、GPUresources: 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 来执行复杂脚本。

规则结构一目了然

下面这个图把规则的核心要素画了出来,方便你日后写规则时对照。

Snakemake 规则结构图 规则定义 rule admet_predict: input output shell / run params resources data/{sample}.sdf results/{sample}.csv logs/{sample}.log 要点回顾 • input / output 支持通配符 {sample},自动批量匹配 • log 捕获标准输出,方便调试失败任务 • params 传递动态参数,resources 限定计算资源 • shell 块用 {variable} 引用,不用加 $

图里把常见的字段都标出来了。你写规则的时候,心里就照着这个模板搭:先把 input/output 想清楚,再决定用 shell 还是 run,最后根据需要补上 log、params、resources。只要结构不乱,流水线就能稳稳地跑起来。