Очень большой ввод и передача с использованием подпроцесса. Открыть - PullRequest
11 голосов
/ 21 октября 2010

У меня довольно простая проблема. У меня есть большой файл, который проходит три этапа: этап декодирования с использованием внешней программы, некоторую обработку в python, а затем перекодирование с использованием другой внешней программы. Я использовал subprocess.Popen (), чтобы попытаться сделать это в Python, а не формировать каналы Unix. Однако все данные буферизируются в памяти. Есть ли питонический способ решения этой задачи, или мне лучше вернуться к простому скрипту на 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 ]

8 голосов
/ 21 октября 2010

Popen имеет параметр bufsize, который будет ограничивать размер буфера в памяти.Если вам вообще не нужны файлы в памяти, вы можете передать файловые объекты в качестве параметров stdin и stdout.Из документа подпроцесса :

bufsize, если задан, имеет то же значение, что и соответствующий аргумент встроенной функции open (): 0 означает небуферизованный, 1 означает строкубуферизованное, любое другое положительное значение означает использование буфера (приблизительно) этого размера.Отрицательный размер буфера означает использование системного значения по умолчанию, что обычно означает полную буферизацию.Значение по умолчанию для bufsize - 0 (без буферизации).

5 голосов
/ 21 октября 2010

Попробуйте сделать это небольшое изменение, посмотрите, будет ли эффективность лучше.

 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))
3 голосов
/ 21 октября 2010

Однако все данные буферизируются в память ...

Используете ли вы subprocess.Popen.communicate()? В принципе, эта функция будет ожидать завершения процесса, все время накапливая данные в буфере, и , а затем вернет их вам. Как вы указали, это проблематично, если вы работаете с очень большими файлами.

Если вы хотите обработать данные во время их генерации, вам нужно написать цикл с использованием методов poll() и .stdout.read(), а затем записать этот вывод в другой сокет / файл / и т. Д.

Обязательно обратите внимание на предупреждения в документации против этого, так как это легко может привести к взаимоблокировке (родительский процесс ожидает, пока дочерний процесс сгенерирует данные, который, в свою очередь, ожидает, пока родительский процесс очистит труба буфера).

1 голос
/ 21 октября 2010

Я использовал метод .read () в потоке stdout. Вместо этого мне просто нужно было читать прямо из потока в цикле 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))
0 голосов
/ 05 мая 2016

Попытка выполнить некоторые основные функции оболочки оболочки с очень большим вводом в python:

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

Я нашел самый простой способ заставить это работать даже с большими (2-5 ГБ) файлами:

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

Мне нравится этот метод, потому что он прост, и вы можете сделать стандартное перенаправление оболочки.

Я попытался пройти маршрут 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() 

Также сломано с очень большими файлами.

...