2010-10-21 12 views
9

Mam dość prosty problem. Mam duży plik, który przechodzi przez trzy kroki, krok dekodowania za pomocą zewnętrznego programu, trochę przetwarzania w python, a następnie przekodowanie za pomocą innego zewnętrznego programu. Używam podprocesu.Popen(), aby spróbować zrobić to w pythonie, zamiast tworzyć rury unixowe. Jednak wszystkie dane są buforowane do pamięci. Czy jest jakiś pythonic sposób wykonania tego zadania, czy najlepiej wracam do prostego skryptu python, który czyta ze stdin i zapisuje na standardowe wyjście z rurami unixowymi po obu stronach?Bardzo duże wejście i orurowanie za pomocą podprocesu.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

Co to jest * duży plik *? – eumiro

+1

Dobre pytanie. Większa niż dostępna pamięć RAM. – seandavi

+0

Głupi błąd z mojej strony. Użyłem metody read() w pętli for powyżej. Poprawiona linia nie powinna oczywiście mieć funkcji .read(), ponieważ samtools.stdout jest w rzeczywistości obiektem podobnym do pliku. – seandavi

Odpowiedz

4

Spróbuj dokonać tej niewielkiej zmiany, sprawdź, czy wydajność jest lepsza.

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

To był problem, anijhaw. Dzięki za uwagę. – seandavi

5

Popen ma parametr bufsize, który ograniczy rozmiar bufora w pamięci. Jeśli nie chcesz w ogóle plików w pamięci, możesz przekazywać obiekty plików jako parametry stdin i stdout. Z subprocess docs:

bufsize, jeśli podano, ma takie samo znaczenie, jak odpowiedni argument wbudowaną funkcją otwartym(): 0 oznacza niebuforowanego 1 oznacza linię zbuforowanym inna wartość dodatnia oznacza użyciu bufora (około) tego rozmiaru. Ujemny bufsize oznacza użycie domyślnego systemu, co zwykle oznacza pełne buforowanie. Domyślną wartością bufsize jest 0 (niebuforowana).

+0

Prosto z tych samych dokumentów, w metodzie "komunikuj się": "Uwaga Odczyt danych jest buforowany w pamięci, więc nie używaj tej metody, jeśli rozmiar danych jest duży lub nieograniczony." –

+0

Wysłałem powyższy kod. Ten kod zdecydowanie prowadzi do procesu Pythona zmierzającego w kierunku stratosfery pod względem wykorzystania pamięci, więc zdecydowanie brakuje mi niektórych szczegółów .... – seandavi

+0

Z dokumentów: Zmieniono w wersji 3.3.1: bufsize teraz domyślnie -1, aby umożliwić buforowanie przez domyślnie pasuje do zachowania, którego oczekuje większość kodu. – cmcginty

1

Jednak wszystkie dane są buforowane w pamięci ...

Używasz subprocess.Popen.communicate()? Z założenia funkcja ta będzie czekać na zakończenie procesu, gromadząc dane w buforze, a następnie , a następnie zwróci je tobie. Jak już zauważyłeś, jest to problematyczne w przypadku bardzo dużych plików.

Jeśli chcesz przetworzyć dane podczas ich generowania, musisz napisać pętlę, używając metod poll() i .stdout.read(), a następnie wypisać to wyjście do innego gniazda/pliku/etc.

Należy pamiętać o ostrzeżeniach w dokumentacji przed wykonaniem tej czynności, ponieważ łatwo jest doprowadzić do zakleszczenia (proces macierzysty czeka na proces potomny, aby wygenerować dane, który z kolei czeka na proces nadrzędny, aby opróżnić bufor do rur).

1

Używałem metody .read() w strumieniu standardowym. Zamiast tego po prostu musiałem przeczytać bezpośrednio ze strumienia w pętli for powyżej. Poprawiony kod spełnia moje oczekiwania.

#!/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

Próbując zrobić kilka podstawowych rurociągów powłoki z bardzo dużego wkładu w Pythonie:

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

znalazłem najprostszy sposób, aby dostać tę pracę nawet przy dużych plikach (2-5GB) został:

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

Podoba mi się ta metoda, ponieważ jest prosta i można wykonać standardowe przekierowanie powłoki.

Próbowałem będzie drogi POPEN uruchomić przekierowanie:

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) 

jednak, że zerwał z dużymi plikami. Za pomocą:

p.stdin.write() 

Złamał również bardzo duże pliki.

+1

1- Niepoprawne jest wywoływanie 'p.communicate()' z danymi wejściowymi więcej niż jeden raz (proces potomny jest nieaktywny, jeśli 'p.communicate()' zwraca). 2 - nie trzeba używać powłoki: 'check_call (['svnadmin', 'load', repo], stdin = input_file)' powinno działać jakkolwiek duże jest 'input_file'. – jfs

+0

@ J.F. Sebastian: Dzięki za informację. – mauricio777

Powiązane problemy