Подтвердить что ты не робот

Чтение процесса 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_envs\\py362_32\\Scripts\\activate",
    "&&"
]
cmd1 = " ".join(base_cmd + ['python -c "import sys; print(sys.version)"'])
cmd2 = " ".join(base_cmd + ["python -m http.server"])

Если я запустил execute(cmd1), вывод будет распечатан без каких-либо проблем.

Однако, если я запустил execute(cmd2), то ничего не будет напечатано, почему это так и как я могу его исправить, чтобы я мог видеть выход http.server в реальном времени.

Также, как for line in p.stdout оценивается внутри? это какая-то бесконечная петля до достижения цели или чего-то еще?

Этот раздел уже был рассмотрен несколько раз здесь, в SO, но я еще не нашел решение для Windows. Вышеприведенный фрагмент фактически является кодом из этого и пытается запустить http.server из virtualenv (python3.6.2-32bits на win7)

4b9b3361

Ответ 1

Если вы хотите непрерывно читать из работающего подпроцесса, вы должны сделать этот процесс "небуферизованным". Ваш подпроцесс является программой Python, это можно сделать, передав -u интерпретатору:

python -u -m http.server

Вот как он выглядит в окне Windows.

введите описание изображения здесь

Ответ 2

С помощью этого кода вы не можете видеть вывод в реальном времени из-за буферизации:

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.

Ответ 3

Как для строки в p.stdout оценивается внутренне? это какая-то бесконечная петля до достижения цели или чего-то еще?

p.stdout - это буфер (блокировка). Когда вы читаете из пустого буфера, вы блокируетесь до тех пор, пока в этот буфер не будет записано что-либо. Как только что-то в нем, вы получаете данные и выполняете внутреннюю часть.

Подумайте о том, как tail -f работает в Linux: он ждет, пока в файл не будет записано что-либо, а когда оно будет эхоново выводить новые данные на экран. Что происходит, когда нет данных? он ждет. Поэтому, когда ваша программа попадает в эту строку, она ждет данных и обрабатывает их.

Как работает ваш код, но при запуске как модели нет, это должно быть связано с этим как-то. Модуль http.server, вероятно, буферизует вывод. Попробуйте добавить параметр -u в Python, чтобы запустить процесс как небуферизованный:

-u: небуферизованный двоичный stdout и stderr; также PYTHONUNBUFFERED = x          см. справочную страницу для подробной информации о внутренней буферизации, относящейся к '-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', ..]

Ответ 4

Я думаю, что основная проблема заключается в том, что http.server каким-то образом записывает вывод в stderr, здесь у меня есть пример с asyncio, считывая данные либо из stdout, либо stderr.

Моя первая попытка состояла в том, чтобы использовать asyncio, хороший API, который существует с Python 3.4. Позже я нашел более простое решение, поэтому вы можете выбрать, оба из них должны работать.

asyncio as solution

В фоновом режиме 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)

Ответ 5

Вы можете реализовать поведение без буфера на уровне ОС.

В Linux вы можете привязать существующую командную строку к stdbuf:

stdbuf -i0 -o0 -e0 YOURCOMMAND

Или в Windows вы можете привязать существующую командную строку к winpty:

winpty.exe -Xallow-non-tty -Xplain YOURCOMMAND

Я не знаю об ОС-нейтральных инструментах для этого.