Чтение процесс stdout в режиме реального времени
давайте рассмотрим этот фрагмент:
from subprocess import Popen, PIPE, CalledProcessError
def execute(cmd):
with Popen(cmd, shell=True, stdout=PIPE, bufsize=1, universal_newlines=True) as p:
for line in p.stdout:
print(line, end='')
if p.returncode != 0:
raise CalledProcessError(p.returncode, p.args)
base_cmd = [
"cmd", "/c", "d:virtual_envspy362_32Scriptsactivate",
"&&"
]
cmd1 = " ".join(base_cmd + ['python -c "import sys; print(sys.version)"'])
cmd2 = " ".join(base_cmd + ["python -m http.server"])
если я запускаю execute(cmd1)
вывод будет напечатан без каких-либо проблем.
однако, если я бегу execute(cmd2)
вместо этого ничего не будет напечатано, почему это и как я могу это исправить, чтобы я мог видеть http.вывод сервера в режиме реального времени.
кроме того, как for line in p.stdout
оценивается внутренне? это какой-то бесконечный цикл, пока не достигнет stdout eof или что-то еще?
эта тема уже была рассмотрена несколько раз здесь в SO, но я еще не нашел решение для windows. Приведенный выше фрагмент фактически является кодом из этого ответ и пытается запустить http.сервер от virtualenv (python3.6.2-32bits на win7)
5 ответов
Если вы хотите читать непрерывно из запущенного подпроцесса, вы должны сделать это вывод процесса без буферизации. Ваш подпроцесс является программой Python, это можно сделать, передав -u
переводчик:
python -u -m http.server
вот как это выглядит в окне Windows.
С помощью этого кода Вы не можете видеть вывод в реальном времени из-за буферизации:
for line in p.stdout:
print(line, end='')
но если вы используете p.stdout.readline()
это должно работать:
while True:
line = p.stdout.readline()
if not line: break
print(line, end='')
посмотреть тегом обсуждение ошибок python дополнительные сведения
UPD: здесь вы можете найти почти то же самое проблема с различными решениями на stackoverflow.
как для линии в п. стандартный вывод были оценены внутренне? это какой-то бесконечный цикл, пока не достигнет stdout eof или что-то еще?
p.stdout
буфер (блокирование). Когда вы читаете из пустой буфер, вы блокируетесь, пока что-то не будет записано в этот буфер. Как только в нем что-то есть, вы получаете данные и выполняете внутреннюю часть.
подумайте, как tail -f
работает на Linux: он ждет, пока что-то пишется файл, и когда он это делает, эхо-это новые данные на экране. Что происходит, когда нет данных? он ждет. поэтому, когда ваша программа попадает в эту строку, она ждет данных и обрабатывает их.
поскольку ваш код работает, но при запуске в качестве модели нет, он должен быть как-то связан с этим. The http.server
модуль, вероятно, буферизует вывод. Попробуйте добавить -u
параметр Python для запуска процесса как unbuffered:
- u: unbuffered binary stdout и stderr; также PYTHONUNBUFFERED=x см. man-страницу для получения подробной информации о внутренней буферизации, относящейся к'- u'
кроме того, вы можете попробовать изменить свой цикл на for line in iter(lambda: p.stdout.read(1), ''):
, а это 1
байт за раз перед обработкой.
обновление: полный код цикла
for line in iter(lambda: p.stdout.read(1), ''):
sys.stdout.write(line)
sys.stdout.flush()
кроме того, вы передаете свою команду как строку. Попробуйте передать его как список, с каждым элементом в своем слоте:
cmd = ['python', '-m', 'http.server', ..]
Я думаю, что главная проблема в том, что http.server
как-то регистрирует вывод в stderr
, вот у меня пример с asyncio
, считывание данных либо из stdout
или stderr
.
моей первой попыткой было использовать asyncio, хороший API, который существует с Python 3.4. Позже я нашел более простое решение, поэтому вы можете выбрать, оба из них должны работать.
asyncio как решение
в фоновом режиме asyncio использует IOCP - API windows для асинхронный материал.
# inspired by https://pymotw.com/3/asyncio/subprocesses.html
import asyncio
import sys
import time
if sys.platform == 'win32':
loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)
async def run_webserver():
buffer = bytearray()
# start the webserver without buffering (-u) and stderr and stdin as the arguments
print('launching process')
proc = await asyncio.create_subprocess_exec(
sys.executable, '-u', '-mhttp.server',
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
print('process started {}'.format(proc.pid))
while 1:
# wait either for stderr or stdout and loop over the results
for line in asyncio.as_completed([proc.stderr.readline(), proc.stdout.readline()]):
print('read {!r}'.format(await line))
event_loop = asyncio.get_event_loop()
try:
event_loop.run_until_complete(run_df())
finally:
event_loop.close()
перенаправление из stdout
на основе вашего примера это действительно простое решение. Он просто перенаправляет stderr в stdout, и читается только stdout.
from subprocess import Popen, PIPE, CalledProcessError, run, STDOUT import os
def execute(cmd):
with Popen(cmd, stdout=PIPE, stderr=STDOUT, bufsize=1) as p:
while 1:
print('waiting for a line')
print(p.stdout.readline())
cmd2 = ["python", "-u", "-m", "http.server"]
execute(cmd2)
вы можете реализовать поведение без буфера на уровне ОС.
в Linux вы можете обернуть существующую командную строку с помощью stdbuf
:
stdbuf -i0 -o0 -e0 YOURCOMMAND
или в Windows, вы можете обернуть существующую командную строку с winpty
:
winpty.exe -Xallow-non-tty -Xplain YOURCOMMAND
Я не знаю об ОС-нейтральных инструментах для этого.