В python, как да проверя stdout от subprocess.Popen обект за нещо за четене?

В python, как да проверя stdout от subprocess.Popen обект за нещо за четене? Пиша обвивка около инструмент, който понякога работи с часове без прекъсване. Използването на .readline() на stdout от дъщерния процес сериозно намалява скоростта на скрипта, когато се изпълнява за повече от няколко минути. Имам нужда от начин да проверя stdout по-ефективно, ако има нещо за четене. Между другото, този конкретен инструмент пише само цели редове наведнъж. Скриптът върви така:

    #!/usr/bin/python -u
    #thiswrap.py

    import sys, time
    from subprocess import *

    chldp = Popen(sys.argv[1], bufsize=0, stdout=PIPE, close_fds=True)
    chstdin,chstdout=chldp.stdin,chldp.stdout
    startnoti=False

    while not chldp.poll():
        rrl=chstdout.readline() # <--- this is where the problem is
        if rrl[-8:]=='REDACTED TEXT':
            sys.stdout.write(rrl[:-1]+'   \r')
            if not startnoti: startnoti=True
        else:
            if startnoti: sys.stdout.write('\n')
            sys.stdout.write(rrl)
            if startnoti: # REDACTED
            time.sleep(0.1)
        time.sleep(0.1)

Някакви идеи?


person Matthew Jensen    schedule 08.08.2011    source източник
comment
Защо е проблем да позволите на readline да блокира? И защо звъниш на sleep?   -  person Vebjorn Ljosa    schedule 08.08.2011
comment
Ще игнорирам частта за троловете относно блокирането на линията за четене и заспиването наистина е просто мярка за спиране, докато не разреша нещата с линията за четене. Знам, че е малко мързелив и тромав, но няма да имам нужда от нищо друго в тази част от кода, освен ако не е нещо, което може да дойде от по-добро решение, за да знам кога да използвам readline(), така че остава там, докато този проблем изчезне далеч.   -  person Matthew Jensen    schedule 10.08.2011


Отговори (3)


Трябва да настроите файловите дескриптори да не блокират, можете да направите това с помощта на fcntl :

import sys, time, fcntl, os
from subprocess import *

chldp = Popen(sys.argv[1], bufsize=0, stdout=PIPE, close_fds=True)
chstdin, chstdout = chldp.stdin, chldp.stdout
fl = fcntl.fcntl(chstdout, fcntl.F_GETFL)
fcntl.fcntl(chstdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)

while chldp.poll() is not None:
    try:
        rrl = chstdout.readline()
    except IOError:
        time.sleep(0.1)
        continue
    # use rrl

Когато няма налични данни, IOError ще бъде повдигнато от readline().

Имайте предвид, че тъй като chldp.poll() може да върне 0, когато подпроцесът приключи, вероятно трябва да използвате childp.poll() is not None във вашия while, а не not childp.poll().

person Andrew Clark    schedule 08.08.2011
comment
Само забележка: това не работи на Windows (знам, че плакатът изглежда използва Linux), вижте stackoverflow.com/questions/375427/ - person agf; 09.08.2011
comment
Това решение не работи. Това кара обвивката не само да пропуска редове, но и така или иначе да извежда IOErrors. Освен това нещото неизбежно се срива в рамките на минути след стартиране. (Между другото, не linux, mac os 10.6 хаха). Освен това дъщерният процес никога няма да се откаже от само себе си, така че запазвам цикъла while такъв, какъвто е. - person Matthew Jensen; 10.08.2011

За съжаление няма готов начин за анкета за условие "има достатъчно данни в канала с нов ред, така че readline() да се върне незабавно".

Ако искате ред наведнъж и не искате да блокирате, можете или да:

Или внедрете свое собствено буфериране чрез клас или генератор и анкета чрез това, напр.:

def linereader():
    data = ""
    while True:
        if poll(f.fd):
            data += f.read(100)
        lines = data.split("\n")
        data = lines[-1]
        for line in lines[:-1]:
            yield line

# use
for line in linereader():
    if line:
       print line
    else:
       time.sleep(...)

Или използвайте нишки (оставено като упражнение за читателя, имайте предвид, че по-старите версии на Python показват грешка, ако стартирате подпроцес от нишка, различна от основната)

person Dima Tisnek    schedule 06.02.2012

Предложеното решение в първия коментар е почти правилно. Просто трябва да предадете целочислен файлов дескриптор като първи аргумент на fcntl.fcntl, а не файловия обект на Python. Взе от друг отговор

Ето кода за промяна:

chstdout = chldp.stdout
fd = chstdout.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
person krydev    schedule 21.07.2021