Python: как заглянуть в объект pty, чтобы избежать блокировки?


Я использую pty для чтения не блокирующего stdout процесса, подобного этому:

import os
import pty
import subprocess

master, slave = pty.openpty()

p = subprocess.Popen(cmd, stdout = slave)

stdout = os.fdopen(master)
while True:
    if p.poll() != None:
        break

    print stdout.readline() 

stdout.close()

Все работает нормально, за исключением того, что while-loop иногда блокируется. Это связано с тем, что строка print stdout.readline() ожидает, что что-то будет прочитано из stdout. Но если программа уже завершилась, мой маленький скрипт там будет висеть вечно.

Мой вопрос: есть ли способ заглянуть в объект stdout и проверить, есть ли данные, доступные для чтения? Если это не так, он должен продолжите через while-loop, где он обнаружит, что процесс фактически уже завершен и разорвать цикл.

2 5

2 ответа:

Да, используйте опрос модуля select:

import select
q = select.poll()
q.register(stdout,select.POLLIN)

И в то же время использовать:

l = q.poll(0)
if not l:
    pass # no input
else:
    pass # there is some input

Выбор .poll () Ответ очень аккуратный, но не работает на Windows. Альтернативой является следующее решение. Он не позволяет вам заглянуть в stdout, но предоставляет неблокирующую альтернативу readline() и основан на этом ответе:

from subprocess import Popen, PIPE
from threading import Thread
def process_output(myprocess): #output-consuming thread
    nextline = None
    buf = ''
    while True:
        #--- extract line using read(1)
        out = myprocess.stdout.read(1)
        if out == '' and myprocess.poll() != None: break
        if out != '':
            buf += out
            if out == '\n':
                nextline = buf
                buf = ''
        if not nextline: continue
        line = nextline
        nextline = None

        #--- do whatever you want with line here
        print 'Line is:', line
    myprocess.stdout.close()

myprocess = Popen('myprogram.exe', stdout=PIPE) #output-producing process
p1 = Thread(target=process_output, args=(dcmpid,)) #output-consuming thread
p1.daemon = True
p1.start()

#--- do whatever here and then kill process and thread if needed
if myprocess.poll() == None: #kill process; will automatically stop thread
    myprocess.kill()
    myprocess.wait()
if p1 and p1.is_alive(): #wait for thread to finish
    p1.join()

Другие решения для неблокирующего чтения были предложены здесь , но не сработали для меня:

  1. решения, требующие чтения строки (в том числе основанные на очереди), всегда блокируются. Это сложно (невозможно?) чтобы убить поток, который выполняет readline. Он погибает только тогда, когда завершается процесс, который его создал, но не тогда, когда погибает процесс производства продукции.
  2. смешивание низкоуровневых fcntl с высокоуровневыми вызовами readline может не работать должным образом, как указывал anonnn.
  3. Использование select.poll() является аккуратным, но не работает на Windows в соответствии с python docs.
  4. Использование сторонних библиотек кажется излишним для этой задачи и добавляет дополнительные зависимости.