Моя проблема дещо інша, тому що я хотів зібрати як stdout, так і stderr з запущеного процесу, але в кінцевому підсумку те саме, оскільки я хотів вивести результат у віджет як його генерований.
Я не хотів вдаватися до багатьох запропонованих обхідних шляхів, використовуючи черги або додаткові теми, оскільки вони не повинні бути необхідними для виконання такого поширеного завдання, як виконання іншого сценарію та збір його результатів.
Прочитавши запропоновані рішення та документи python, я вирішив свою проблему із реалізацією нижче. Так, він працює лише для POSIX, оскільки я використовую select
виклик функції.
Я погоджуюся, що документи заплутані, і реалізація незручна для такого поширеного сценарію. Я вважаю, що в старих версіях python є різні за замовчуванням Popen
і різні пояснення, так що це створювало багато плутанини. Це, здається, добре працює і для Python 2.7.12 та 3.5.2.
Ключовим моментом було встановити bufsize=1
буферизацію рядків, а потім universal_newlines=True
обробити як текстовий файл, а не двійковий, який, як видається, стане типовим при налаштуванні bufsize=1
.
class workerThread(QThread):
def __init__(self, cmd):
QThread.__init__(self)
self.cmd = cmd
self.result = None ## return code
self.error = None ## flag indicates an error
self.errorstr = "" ## info message about the error
def __del__(self):
self.wait()
DEBUG("Thread removed")
def run(self):
cmd_list = self.cmd.split(" ")
try:
cmd = subprocess.Popen(cmd_list, bufsize=1, stdin=None
, universal_newlines=True
, stderr=subprocess.PIPE
, stdout=subprocess.PIPE)
except OSError:
self.error = 1
self.errorstr = "Failed to execute " + self.cmd
ERROR(self.errorstr)
finally:
VERBOSE("task started...")
import select
while True:
try:
r,w,x = select.select([cmd.stdout, cmd.stderr],[],[])
if cmd.stderr in r:
line = cmd.stderr.readline()
if line != "":
line = line.strip()
self.emit(SIGNAL("update_error(QString)"), line)
if cmd.stdout in r:
line = cmd.stdout.readline()
if line == "":
break
line = line.strip()
self.emit(SIGNAL("update_output(QString)"), line)
except IOError:
pass
cmd.wait()
self.result = cmd.returncode
if self.result < 0:
self.error = 1
self.errorstr = "Task terminated by signal " + str(self.result)
ERROR(self.errorstr)
return
if self.result:
self.error = 1
self.errorstr = "exit code " + str(self.result)
ERROR(self.errorstr)
return
return
ERROR, DEBUG і VERBOSE - просто макроси, які друкують вихід на термінал.
Це рішення є ефективним на 99,99% IMHO, оскільки воно все ще використовує функцію блокування readline
, тому ми припускаємо, що підпроцес є хорошим та виводить повні рядки.
Я вітаю відгуки, щоб покращити рішення, оскільки я все ще новачок у Python.