2010-10-21 17 views
9

Ich habe ein ziemlich einfaches Problem. Ich habe eine große Datei, die drei Schritte durchläuft, einen Decodierschritt, der ein externes Programm verwendet, etwas, das in Python verarbeitet wird, und dann mit einem anderen externen Programm umcodiere. Ich habe subprocess.Popen() verwendet, um dies in Python zu tun, anstatt Unix-Pipes zu bilden. Alle Daten werden jedoch im Arbeitsspeicher gepuffert. Gibt es eine pythonische Art, diese Aufgabe zu erledigen, oder sollte ich am besten auf ein einfaches Python-Skript zurückkommen, das von stdin liest und mit Unix-Pipes auf beiden Seiten nach stdout schreibt?Sehr große Eingabe und Verrohrung mit 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

Was ist * eine große Datei *? – eumiro

+1

Gute Frage. Größer als verfügbarer RAM. – seandavi

+0

Dummer Fehler meinerseits. Ich habe die read() -Methode in der obigen for-Schleife verwendet. Die korrigierte Zeile sollte natürlich nicht die Datei .read() haben, da amtools.stdout eigentlich ein dateiähnliches Objekt ist. – seandavi

Antwort

4

Versuchen Sie, diese kleine Änderung vorzunehmen, um zu sehen, ob die Effizienz besser ist.

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

Das war das Problem, Anijhaw. Danke fürs bemerken. – seandavi

5

Popen hat einen bufsize Parameter, der die Größe des Puffers im Speicher begrenzen. Wenn Sie die Dateien überhaupt nicht im Speicher haben möchten, können Sie Dateiobjekte wie die Parameter stdin und stdout übergeben. Vom subprocess docs:

BufSize, falls gegeben, die gleiche Bedeutung wie das entsprechende Argument für den Einbau-open() Funktion: 0 bedeutet, ungepufferte, 1 bedeutet, Zeile gepuffert andere positiver Wert bedeutet einen Puffer verwenden, von (ungefähr) dieser Größe. Eine negative Bufsize bedeutet, den Systemstandard zu verwenden, der normalerweise vollständig gepuffert bedeutet. Der Standardwert für bufsize ist 0 (ungepuffert).

+0

Direkt aus der gleichen Dokumentation, unter der Methode "communicate": "Hinweis Die gelesenen Daten werden im Speicher zwischengespeichert. Verwenden Sie diese Methode daher nicht, wenn die Datengröße groß oder unbegrenzt ist." –

+0

Ich habe den obigen Code gepostet. Dieser Code führt definitiv dazu, dass der Python-Prozess im Hinblick auf die Speichernutzung in Richtung Stratosphäre geht. Daher fehlen mir definitiv einige Details. – seandavi

+0

Von docs: Geändert in Version 3.3.1: bufsize wird jetzt standardmäßig auf -1 gesetzt, um die Pufferung zu aktivieren Standard, um dem Verhalten zu entsprechen, das der meiste Code erwartet. – cmcginty

1

jedoch alle Daten in den Speicher gepuffert ...

Sie verwenden subprocess.Popen.communicate()? Standardmäßig wartet diese Funktion auf den Abschluss des Prozesses, währenddessen die Daten in einem Puffer gespeichert werden, und dann es Ihnen zurückgeben. Wie Sie bereits festgestellt haben, ist dies problematisch, wenn Sie mit sehr großen Dateien arbeiten.

Wenn Sie die Daten verarbeiten möchten, während sie generiert werden, müssen Sie eine Schleife mit den Methoden poll() und .stdout.read() schreiben und dann diese Ausgabe in einen anderen Socket/Datei/etc schreiben.

Achten Sie darauf, die Warnungen in der Dokumentation zu beachten, da dies leicht zu einem Deadlock führen kann (der übergeordnete Prozess wartet darauf, dass der untergeordnete Prozess Daten generiert, der wiederum darauf wartet, dass der übergeordnete Prozess leer wird) der Rohrpuffer).

1

Ich verwendete die .read() -Methode für den Stdout-Stream. Stattdessen musste ich einfach direkt aus dem Stream in der obigen for-Schleife lesen. Der korrigierte Code macht das, was ich erwartet habe.

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

Der Versuch, einige grundlegende Shell-Rohrleitungen mit sehr großen Eingang in Python zu tun:

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

ich die einfachste Weg, um diese Arbeit auch bei großen (2-5GB) Dateien zu erhalten gefunden:

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

Ich mag diese Methode, weil es einfach ist und Sie Standard-Shell-Umleitung tun können.

Ich versuchte, den Popen Weg zu gehen, eine Umleitung zu laufen:

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) 

Aber das brach mit großen Dateien. Verwendung:

p.stdin.write() 

Auch brach mit sehr großen Dateien.

+1

1- Es ist falsch, 'p.communicate()' mit der Eingabe mehr als einmal aufzurufen (der Kindprozess ist tot, wenn 'p.communicate()' zurückkommt). 2- keine Notwendigkeit, die Shell zu verwenden: 'check_call (['svnadmin', 'laden', Repo], stdin = Eingabe_Datei)' sollte funktionieren, aber große 'Input_file' ist. – jfs

+0

@ J.F. Sebastian: Danke für die Info. – mauricio777