2010-10-21 74 views
9

我有一個非常簡單的問題。我有一個大文件需要經過三個步驟,一個解碼步驟使用外部程序,一些使用python進行處理,然後使用另一個外部程序進行重新編碼。我一直在使用subprocess.Popen()來嘗試在Python中執行此操作,而不是形成unix管道。但是,所有數據都會緩衝到內存中。有沒有pythonic方式做這個任務,或者我最好回到一個簡單的python腳本,從標準輸入讀取並寫入標準輸出與任何一方unix管道?非常大的輸入和管道使用subprocess.Popen

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

什麼是*大文件*? – eumiro 2010-10-21 19:23:21

+1

好問題。大於可用RAM。 – seandavi 2010-10-21 19:30:46

+0

我的部分愚蠢的錯誤。我在上面的for循環中使用了read()方法。當然,更正後的行應該沒有.read(),因爲samtools.stdout實際上是一個類文件對象。 – seandavi 2010-10-21 19:50:19

回答

4

試着做出這個小小的改變,看看效率是否更好。

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

這是問題,anijhaw。感謝您的注意。 – seandavi 2010-10-21 19:54:32

5

Popen有一個bufsize參數,它會限制內存中緩衝區的大小。如果根本不需要內存中的文件,則可以通過stdinstdout參數傳遞文件對象。從subprocess docs

BUFSIZE,如果給定的,具有如對應的參數到內置open()函數相同:緩衝0表示無緩衝,1表示線,任何其它正值表示使用的緩衝(大約)那麼大。負bufsize意味着使用系統默認值,通常意味着完全緩衝。 bufsize的默認值是0(無緩衝)。

+0

直接來自同一個文檔,在'communic'方法下:「注意數據讀取緩衝在內存中,所以如果數據量很大或者無限,就不要使用這個方法。」 – 2010-10-21 19:41:53

+0

我發佈了上面的代碼。這段代碼肯定會導致python進程在內存使用方面朝向平流層,因此我肯定錯過了一些細節...... – seandavi 2010-10-21 19:46:24

+0

來自文檔:版本3.3.1中已更改:bufsize現在默認爲-1以啓用緩衝默認匹配大多數代碼所期望的行爲。 – cmcginty 2018-02-23 22:40:20

1

然而,所有的數據緩存到內存...

是否使用subprocess.Popen.communicate()?通過設計,該功能將等待該過程完成,同時將數據累積在緩衝區中,然後然後將其返回給您。正如你所指出的,如果處理非常大的文件,這是有問題的。

如果要在生成數據時處理數據,則需要使用poll().stdout.read()方法編寫一個循環,然後將該輸出寫入另一個套接字/文件/等。

請務必注意文檔中的警告,因爲這很容易導致死鎖(父進程等待子進程生成數據,然後等待父進程清空管道緩衝區)。

1

我在標準輸出流上使用了.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)) 
-1

試圖做一些基本的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() 

還打破了非常大的文件。

+1

1-不止一次使用輸入調用'p.communicate()'(如果'p.communicate()'返回,子進程已經死了)。 2-不需要使用shell:'check_call(['svnadmin','load',repo],stdin = input_file)'應該可以工作,但是大'input_file'是。 – jfs 2016-05-05 20:20:44

+0

@ J.F。塞巴斯蒂安:謝謝你的信息。 – mauricio777 2016-05-06 01:19:01