使用subprocess.Popen处理非常大的输入和管道
我遇到了一个比较简单的问题。我有一个很大的文件,需要经过三个步骤处理:首先用一个外部程序解码,然后用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 个回答
不过,所有的数据都是先存到内存里的……
你是不是在用subprocess.Popen.communicate()
这个功能?这个功能的设计是会等到进程完成后,才会把所有的数据一次性返回给你,同时在这个过程中会把数据存到一个缓冲区里。正如你所提到的,这在处理非常大的文件时会有问题。
如果你想在数据生成的同时就处理它,你需要写一个循环,使用poll()
和.stdout.read()
这两个方法,然后把输出写到另一个套接字、文件等地方。
一定要注意文档中的警告,因为这样做很容易导致死锁(父进程在等子进程生成数据,而子进程又在等父进程清空管道缓冲区)。
试着做这个小改动,看看效率是否更好。
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))
Popen 有一个 bufsize
参数,这个参数可以限制内存中缓冲区的大小。如果你不想在内存中存放文件,可以把文件对象作为 stdin
和 stdout
参数传进去。根据 subprocess 文档 的说明:
bufsize
参数的含义和内置的 open() 函数中的对应参数是一样的:0 表示不使用缓冲,1 表示按行缓冲,其他正值表示使用大约那个大小的缓冲区。负值的bufsize
表示使用系统默认的设置,通常这意味着使用完全缓冲。bufsize
的默认值是 0(不缓冲)。