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, где обнаружит, что процесс на самом деле уже завершен, и разорвет цикл.


person Woltan    schedule 21.06.2011    source источник
comment
Просто идея, не знаю, работает ли это, но: может быть, вы могли бы проверить, выдает ли stdout.seek(1, os.SEEK_CUR) исключение, и таким образом определить, есть ли доступные данные?   -  person Jacob    schedule 21.06.2011
comment
@cularis stdout.seek(1, os.SEEK_CUR) выдает это исключение: IOError: [Errno 29] Illegal seek   -  person Woltan    schedule 21.06.2011
comment
Общий комментарий: используйте p.poll() is not None (проверка идентичности объекта), а не p.poll() != None (проверка равенства, менее точная и медленная).   -  person Chris Morgan    schedule 27.06.2011
comment
Хотя ваш цикл может иногда блокироваться, он никогда не должен зависать на неопределенный срок, потому что закрываемый файл считается готовым к чтению опросом.   -  person Jan Hudec    schedule 27.06.2011
comment
type(p) is subprocess.Popen doc for subprocess.Popen.poll() is Проверить, не завершился ли дочерний процесс. Возвращает атрибут кода возврата.   -  person Dan D.    schedule 27.06.2011
comment
@Chris Спасибо за комментарий, в будущем я буду использовать проверку идентификации объекта, если это уместно, как в случае выше.   -  person Woltan    schedule 27.06.2011
comment
@Jan К сожалению, у меня есть случай, когда он блокируется. Если я нахожусь в строке print stdout.readline(), мой скрипт будет зависать до тех пор, пока некоторый вывод не будет записан в stdout. Если процесс тем временем завершится, мой скрипт все равно зависнет.   -  person Woltan    schedule 27.06.2011


Ответы (2)


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

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
person Dan D.    schedule 27.06.2011
comment
Я не думаю, что он делает что-то отличное от метода опроса дескриптора процесса, т.е. не решает проблему. - person Jan Hudec; 27.06.2011
comment
в то время как метод опроса дескриптора процесса проверяет конец процесса, это проверит, есть ли ввод stdout, ожидающий чтения. - person Dan D.; 27.06.2011
comment
Ах хорошо. Если опрос процесса проверяет только то, что программа завершилась (хм, как я мог думать, что она делает что-то еще -- она ​​не знает о канале ради $deity), она могла бы никогда не работать разумно . - person Jan Hudec; 27.06.2011

Ответ select.poll() очень аккуратный, но не работает в Windows. Следующее решение является альтернативой. Он не позволяет просматривать стандартный вывод, но предоставляет неблокирующую альтернативу 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=(myprocess,)) #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 (в том числе основанные на Queue), всегда блокируются. Трудно (невозможно?) убить поток, выполняющий readline. Он уничтожается только тогда, когда процесс, который его создал, завершается, но не тогда, когда завершается процесс, производящий вывод.
  2. Смешивание низкоуровневого fcntl с высокоуровневыми вызовами readline может работать неправильно, как указал anonnn.
  3. Использование select.poll() удобно, но не работает в Windows в соответствии с документацией по python.
  4. Использование сторонних библиотек кажется излишним для этой задачи и добавляет дополнительные зависимости.
person Vikram Pudi    schedule 15.03.2013
comment
Что такое dcmpid? - person PascalVKooten; 27.01.2018
comment
@PascalVKooten Во время вырезания-вставки я не переименовал dcmpid в myprocess. Теперь исправлено. - person Vikram Pudi; 12.05.2021