使用subprocess.Popen进行非常大的输入和管道输入

时间:2010-10-21 19:20:38

标签: python subprocess popen

我有一个非常简单的问题。我有一个大文件,经过三个步骤,使用外部程序的解码步骤,python中的一些处理,然后使用另一个外部程序重新编码。我一直在使用subprocess.Popen()来尝试在python中执行此操作,而不是形成unix管道。但是,所有数据都缓冲到内存中。有没有pythonic方式来完成这个任务,或者我最好回到一个简单的python脚本,它从stdin读取并写入stdout,两边都有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 个答案:

答案 0 :(得分:8)

Popen有一个bufsize参数,它将限制内存中缓冲区的大小。如果您根本不想要内存中的文件,则可以将文件对象作为stdinstdout参数传递。来自subprocess docs

  

bufsize,如果给定,与内置open()函数的对应参数具有相同的含义:0表示无缓冲,1表示行缓冲,任何其他正值表示使用(大约)该大小的缓冲区。负bufsize意味着使用系统默认值,这通常意味着完全缓冲。 bufsize的默认值为0(无缓冲)。

答案 1 :(得分:5)

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

 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))

答案 2 :(得分:3)

  

但是,所有数据都缓冲到内存......

您使用的是subprocess.Popen.communicate()吗?按照设计,此函数将等待进程完成,同时将数据累积到缓冲区中,然后将其返回给您。正如您所指出的,如果处理非常大的文件,这是有问题的。

如果要在生成数据时处理数据,则需要使用poll().stdout.read()方法编写循环,然后将该输出写入另一个套接字/文件/等。< / p>

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

答案 3 :(得分:1)

我在stdout流上使用.read()方法。相反,我只需要在上面的for循环中直接从流中读取。纠正后的代码符合我的预期。

#!/usr/bin/env python
import os
import sys
import 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:
        if(line.startswith("@")):
            samtoolsout.stdin.write(line)
        else:
            linesplit = line.split("\t")
            if(linesplit[10]=="*"):
                linesplit[9]="*"
            samtoolsout.stdin.write("\t".join(linesplit))

答案 4 :(得分:-1)

尝试在python中使用非常大的输入执行一些基本的shell管道:

svnadmin load /var/repo < r0-100.dump

我发现即使使用大型(2-5GB)文件,最简单的方法就是:

subprocess.check_output('svnadmin load %s < %s' % (repo, fname), shell=True)

我喜欢这种方法,因为它很简单,你可以做标准的shell重定向。

我尝试使用Popen路线来运行重定向:

cmd = 'svnadmin load %s' % repo
p = Popen(cmd, stdin=PIPE, stdout=PIPE, shell=True)
with open(fname) as inline:
    for line in inline:
        p.communicate(input=line)

但这打破了大文件。使用:

p.stdin.write() 

还打破了非常大的文件。