配置文件和参数:使用config.yaml、命令行参数、参数传递

Snakemake 工作流跑起来简单,但一旦要适应不同批次、不同样本、不同路径,你就得学会跟配置打交道。我刚开始写工作流时,什么都硬编码在 Snakefile 里,结果换一台机器、换一批数据就崩。后来老老实实用起 config.yaml,嗯,真香。说白了,配置管理就是让工作流跟数据、环境解耦。你想想看,把样本 ID、参考基因组路径、质控阈值这些东西写死在脚本里,跟把钥匙锁在保险柜里有什么区别?

一句话总结:config.yaml 是工作流的“总控台”,命令行参数是“临时遥控器”,两者配合好,工作流才能到处跑。

1. 认识 config.yaml —— 工作流的“总控台”

config.yaml 是 YAML 格式的配置文件,Snakemake 启动时会自动读取。你只需要在 Snakefile 同级目录下放一个 config.yaml,或者在命令行用 --configfile 指定路径。

一个典型的 config.yaml 长这样:

# config.yaml
samples: ["S01", "S02", "S03"]
reference: "/data/genomes/hg38.fa"
fastq_dir: "/raw_data/fastq"
output_dir: "/results/qc"
qc_threshold: 30
adapters:
  - "AGATCGGAAGAGC"
  - "CTGTCTCTTATAC"

我个人习惯把所有可能变动的参数都扔进 config.yaml。比如样本列表、路径、阈值、数据库版本。这样换项目时,改一个文件就行,不用动 Snakefile 的代码逻辑。我在项目中遇到过有人把路径写死在 Snakefile 的 input: 里,结果从服务器迁到 HPC 集群时,每个路径都要改一遍,惨不忍睹。

注意:YAML 对缩进敏感,不要用 Tab,一律用空格。我见过同事因为编辑器自动插入了 Tab,Snakemake 报错 “expected ”,排查了半小时。

2. 命令行参数优先级与覆盖机制

Snakemake 的参数有明确的优先级规则:命令行参数 > config.yaml > Snakefile 中的默认值。你跑工作流时,如果临时想改一个阈值,不用改配置文件,直接加 --config qc_threshold=25 就行。这个机制特别适合调试。

举个例子:

# 默认使用 config.yaml 中的 qc_threshold=30
snakemake --cores 4

# 临时改为 25,不修改文件
snakemake --cores 4 --config qc_threshold=25

覆盖规则其实很直观:

来源优先级说明
命令行 --config最高临时覆盖,不影响 config.yaml 文件
config.yaml 文件中等工作流的标准配置来源
Snakefile 中定义的默认值最低仅当 config.yaml 中未定义时才生效

为什么会这样设计?你想想看,生产环境用 config.yaml 做持久化配置,开发调试时用命令行快速试参数,两不耽误。我经常在跑大规模质控前,先用 --config UMI_reads=1000 跑一小批验证,没问题了再撤掉这个参数跑全量。

3. 在 Snakefile 中读取和使用参数

Snakefile 里通过全局字典 config 来访问配置内容。比如:

# Snakefile
REFERENCE = config["reference"]
THRESHOLD = config.get("qc_threshold", 20)  # 安全取值,带默认
SAMPLES = config["samples"]

rule fastqc:
    input:
        f"{{sample}}_R1.fastq"
    output:
        f"{{sample}}_fastqc.html"
    params:
        threshold = THRESHOLD
    run:
        shell("fastqc {input} -o {output} --threshold {params.threshold}")

注意 config.get() 比 config["key"] 安全,当 key 缺失时不会直接崩溃。我踩过一个坑:配置文件里漏写了 output_dir,Snakemake 直接报 KeyError,整个工作流卡住。后来我改用 config.get("output_dir", "./results") 并加一条检查规则,遇到缺失就打印警告并退出。

参数类型转换:YAML 读取后,数字还是数字,布尔还是布尔,列表还是列表。但如果你从命令行传参数,--config sample_list="['S01','S02']",字符串就会被当成字符串,不会自动解析成 Python 列表。我曾经因此把样本列表传成了带引号的字符串,结果 for s in sample_list 只遍历了字符。解决方法是:要么用 YAML 文件传列表,要么在 Snakefile 里用 ast.literal_eval 转一下。

小技巧:在 Snakefile 开头打印所有 config 条目,方便调试。加一行 print("Config:", config),运行时你能看到实际拿到的值。

4. 参数传递的常见模式

生产环境中,我通常用三层参数传递模式:

  1. 默认值写在 Snakefile 的 config.get() 里,用于最通用的场景。
  2. 项目级配置放在 config.yaml 中,覆盖默认值,包含样本路径、参考基因组等。
  3. 运行级覆盖通过命令行 --config 临时调整,比如只跑部分样本或跳过某个规则。

举个例子,我在做批次质控时,config.yaml 包含所有批次的公共路径,但运行某批时用命令行指定批次 ID:

# config.yaml
raw_root: "/data/batches"
output_root: "/results/qc"
qc_tool: "fastp"
params:
  fastp: "-q 20 -l 50"
# 命令行
snakemake --cores 8 --config batch_id="BATCH_2024_03"

然后 Snakefile 里拼接路径:

BATCH = config["batch_id"]
RAW_DIR = f"{config['raw_root']}/{BATCH}"
OUT_DIR = f"{config['output_root']}/{BATCH}"

我曾经在团队里因为忘记传 batch_id,导致工作流用了空字符串拼接,路径变成了 /data/batches//raw_data,虽然没报错但读错了文件。后来加了强制检查:

if not config.get("batch_id"):
    raise ValueError("必须提供 batch_id!")
避坑指南:我曾经在一台集群上同时跑多个工作流实例,因为 config.yaml 里写了相同的输出目录,结果两个实例互相覆盖了结果。后来我坚持把输出目录做成参数化,每个运行实例都用独特 ID 来区分。

5. 实战:批次质控工作流的配置设计

现在我们把前面学的综合起来,设计一个批次质控工作流的 config.yaml。假设我们要对 3 个样本做 FastQC 和 MultiQC:

# config.yaml
# 批次信息
batch_id: "batch_001"
sample_ids: ["S01", "S02", "S03"]

# 数据路径
raw_dir: "/data/raw/{batch_id}"
output_dir: "/results/qc/{batch_id}"

# 质控参数
fastqc_threads: 4
cutadapt_adapters: ["AGATCGGAAGAGC", "CTGTCTCTTATAC"]
min_read_length: 36

# 参考基因组(可选)
reference: "/ref/hg38.fa"

# 规则开关
run_multiqc: true

在 Snakefile 中,你会这样使用:

BATCH = config["batch_id"]
RAW = config["raw_dir"].format(batch_id=BATCH)
OUT = config["output_dir"].format(batch_id=BATCH)
SAMPLES = config["sample_ids"]

rule all:
    input:
        expand(f"{OUT}/{{sample}}_fastqc.html", sample=SAMPLES),
        f"{OUT}/multiqc_report.html" if config.get("run_multiqc", False) else []

关键参数说明:

参数名用途默认值说明
batch_id批次标识无(必填)用于区分不同批次的数据和结果
sample_ids样本列表无YAML 列表格式
raw_dir原始数据模板路径"/data/raw/{batch_id}"支持 {batch_id} 格式化
fastqc_threadsFastQC 线程数4根据集群核心数调整
cutadapt_adapters接头序列列表两个常见接头支持多个接头
run_multiqc是否运行 MultiQCtrue灵活开关
核心原则:所有“可能变”的东西都放到 config.yaml 里,所有“不可能变”的(比如工具版本、固定参数)放到 Snakefile 或环境变量里。不要贪心,也别吝啬。
Snakemake 参数传递与优先级流程 命令行 --config config.yaml 文件 Snakefile 默认值 参数合并与优先级裁决 config 全局字典 规则中通过 config[] 或 config.get() 使用 优先级 命令行 > YAML YAML > 默认值

这张图把参数从三个源头(命令行、config.yaml、Snakefile 默认值)到最终被规则使用的完整路径画出来了。中间有个“优先级裁决”环节,确保命令行的值覆盖一切。你写工作流时,脑子里过一遍这个流程,基本就不会出参数打架的问题。

好了,配置和参数的内容就这些。记住:写 Snakefile 时多用 config.get(),少用硬编码;跑工作流时多用 --config 调试;任何可能变化的值都要从外部读取。这样你的工作流才能像积木一样,灵活组合。下一步你可以开始考虑如何把这些配置跟实际的数据批次结合起来了——当然,这就是后面几章的内容了。