Snakemake中单未知变量场景下expand()函数的正确使用方法
你当前的写法在Snakemake 7.0及以上版本执行dry run不会报错,也能生成符合预期的文件路径,但存在两个明确的隐患,不属于可靠的生产级写法:
- 跨版本兼容性差:7.0以下的旧版本Snakemake中,
expand()默认会尝试替换所有占位符,你在expand中保留{sample}作为通配符的写法不会被自动识别,会直接把字符串{sample}作为字面量拼入输出路径,导致运行报错。 - 可维护性差:直接返回无命名的输出列表后,后续在shell、run块中调用输出文件只能靠位置索引(如
output[0]),一旦调整config['workreq']里的扩展名顺序,所有索引对应的文件都会错位,排查成本极高。
针对你这种「同一文件前缀、批量追加不同扩展名生成输出」的场景,有两种经过验证的可靠写法,全版本Snakemake兼容:
方案1:使用内置multiext函数(优先推荐)
multiext是Snakemake专门为同根多后缀场景设计的内置函数,比手写expand更简洁,通配符解析逻辑稳定,不需要额外加参数。
你只需要保持现有config配置不变(config['workreq']存带.开头的扩展名列表即可),把规则output块修改为如下形式:
rule gridss_preprocess: input: ref=config['ref'], bam=config['bamdir'] + "{sample}.dedup.downsampled.bam", bai=config['bamdir'] + "{sample}.dedup.downsampled.bam.bai" output: multiext(config['bamdir'] + "{sample}.dedup.downsampled.bam", *config['workreq'])
该写法生成的输出路径和你预期完全一致,以样本S1为例,会自动生成你列出的4个目标文件。
方案2:带命名键的输出写法(适合需单独调用单个输出的场景)
如果你后续需要在规则中单独引用某一个固定扩展名的文件(比如单独提取cigar_metrics文件做统计),可以用字典推导式给每个输出绑定和扩展名对应的命名键,彻底避免位置索引带来的错位问题:
rule gridss_preprocess: input: ref=config['ref'], bam=config['bamdir'] + "{sample}.dedup.downsampled.bam", bai=config['bamdir'] + "{sample}.dedup.downsampled.bam.bai" output: # 自动以扩展名(去掉开头的.)作为输出键名 {ext.lstrip('.'): config['bamdir'] + "{sample}.dedup.downsampled.bam" + ext for ext in config['workreq']}
用该写法后,你可以直接通过键名调用对应输出,比如output.cigar_metrics、output.coverage_blacklist_bed,不需要关心扩展名在列表里的顺序。
如果你坚持要用expand()实现,必须显式添加allow_missing=True参数,告诉函数{sample}是规则通配符不需要当场替换,才能保证跨版本兼容,修正后的写法如下:
output: expand( config['bamdir'] + "{sample}.dedup.downsampled.bam{ext}", ext = config['workreq'], sample = "{sample}", allow_missing=True )
不要依赖Snakemake的自动通配符匹配扫描文件,建议在工作流顶层显式提取所有符合命名规则的样本,通过rule all指定最终生成目标,避免漏跑、错配样本:
import os import re # 从bam目录自动提取所有S开头的样本名 SAMPLES = [] for fname in os.listdir(config['bamdir']): match = re.match(r"^(S\d+)\.dedup\.downsampled\.bam$", fname) if match: SAMPLES.append(match.group(1)) # 顶层目标规则,放在所有业务规则最前面 rule all: input: expand( multiext(config['bamdir'] + "{sample}.dedup.downsampled.bam", *config['workreq']), sample = SAMPLES )
注意:顶层rule all里的expand()不要加allow_missing参数,这里需要把所有样本对应的所有输出文件完全展开,明确告诉Snakemake需要生成的全部目标。
内容的提问来源于stack exchange,提问作者James

