使用subprocess.Popen处理非常大的输入和管道

15 投票
5 回答
36315 浏览
提问于 2025-04-16 05:52

我遇到了一个比较简单的问题。我有一个很大的文件,需要经过三个步骤处理:首先用一个外部程序解码,然后用Python进行一些处理,最后再用另一个外部程序进行编码。我一直在尝试用subprocess.Popen()在Python中完成这个过程,而不是使用Unix管道。不过,所有的数据都是先缓存在内存里的。请问有没有更“Python风格”的方法来完成这个任务,还是说我最好还是回到一个简单的Python脚本,直接从标准输入读取数据,然后把结果写到标准输出,前后用Unix管道连接呢?

import os, sys, subprocess

def main(infile,reflist):
    print infile,reflist
    samtoolsin = subprocess.Popen(["samtools","view",infile],
                                  stdout=subprocess.PIPE,bufsize=1)
    samtoolsout = subprocess.Popen(["samtools","import",reflist,"-",
                                    infile+".tmp"],stdin=subprocess.PIPE,bufsize=1)
    for line in samtoolsin.stdout.read():
        if(line.startswith("@")):
            samtoolsout.stdin.write(line)
        else:
            linesplit = line.split("\t")
            if(linesplit[10]=="*"):
                linesplit[9]="*"
            samtoolsout.stdin.write("\t".join(linesplit))

5 个回答

4

不过,所有的数据都是先存到内存里的……

你是不是在用subprocess.Popen.communicate()这个功能?这个功能的设计是会等到进程完成后,才会把所有的数据一次性返回给你,同时在这个过程中会把数据存到一个缓冲区里。正如你所提到的,这在处理非常大的文件时会有问题。

如果你想在数据生成的同时就处理它,你需要写一个循环,使用poll().stdout.read()这两个方法,然后把输出写到另一个套接字、文件等地方。

一定要注意文档中的警告,因为这样做很容易导致死锁(父进程在等子进程生成数据,而子进程又在等父进程清空管道缓冲区)。

6

试着做这个小改动,看看效率是否更好。

 for line in samtoolsin.stdout:
        if(line.startswith("@")):
            samtoolsout.stdin.write(line)
        else:
            linesplit = line.split("\t")
            if(linesplit[10]=="*"):
                linesplit[9]="*"
            samtoolsout.stdin.write("\t".join(linesplit))
13

Popen 有一个 bufsize 参数,这个参数可以限制内存中缓冲区的大小。如果你不想在内存中存放文件,可以把文件对象作为 stdinstdout 参数传进去。根据 subprocess 文档 的说明:

bufsize 参数的含义和内置的 open() 函数中的对应参数是一样的:0 表示不使用缓冲,1 表示按行缓冲,其他正值表示使用大约那个大小的缓冲区。负值的 bufsize 表示使用系统默认的设置,通常这意味着使用完全缓冲。bufsize 的默认值是 0(不缓冲)。

撰写回答