Python: Как транслировать из стандартного вывода подпроцесса - PullRequest
1 голос
/ 04 мая 2020

Я нацелен на реализацию крошечного Python написанного скрипта для упрощения работы с Jupyter.

Поэтому я написал этот скрипт:

import signal
import socket
import subprocess
import sys

sp = None
port = 8888


def get_own_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(('1.1.1.1', 1))
        IP = s.getsockname()[0]
    except:
        IP = '127.0.0.1'
    finally:
        s.close()
    return IP


def signal_handler(sig, frame):
    # terminates Jupyter by sending two SIGINTs to it
    if sp is not None:
        # send termination to jupyter
        sp.send_signal(signal.SIGINT)
        sp.send_signal(signal.SIGINT)

        sys.exit(0)


if __name__ == "__main__":

    own_ip = get_own_ip()

    sp = subprocess.Popen(["jupyter-notebook"
                           , "--ip='%s'" % own_ip
                           , "--port=%i" % port
                           , "--no-browser"],
                          stdout=subprocess.PIPE,
                          stdin=subprocess.PIPE,
                          bufsize=1)

    print(sp)

    signal.signal(signal.SIGINT, signal_handler)

    with sp.stdout:
        print('read')
        for line in sp.stdout.readline():
            print('line: %s' % line)
    print('wait')
    sp.wait()  # wait for the subprocess to exit
* 1005 чтобы использовать его как аргумент для Jupyter. Затем я запускаю Jupyter, а затем хочу отфильтровать некоторый вывод Jupyter (stdout), пока Jupyter работает. Но кажется, что sp.stdout.readline() блокирует.

Приведенный выше код выводит на терминал следующий вывод:

/usr/bin/python3.6 /home/alex/.scripts/get_own_ip.py
<subprocess.Popen object at 0x7fa956374240>
read
[I 22:43:31.611 NotebookApp] Serving notebooks from local directory: /home/alex/.scripts
[I 22:43:31.611 NotebookApp] The Jupyter Notebook is running at:
[I 22:43:31.611 NotebookApp] http://192.168.18.32:8888/?token=c4b7784d784206fc357b8f484b8d659fed6a2b1733b46ae6
[I 22:43:31.611 NotebookApp]  or http://127.0.0.1:8888/?token=c4b7784d784206fc357b8f484b8d659fed6a2b1733b46ae6
[I 22:43:31.611 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 22:43:31.614 NotebookApp] 

    To access the notebook, open this file in a browser:
        file:///home/alex/.local/share/jupyter/runtime/nbserver-18280-open.html
    Or copy and paste one of these URLs:
        http://192.168.18.32:8888/?token=c4b7784d784206fc357b8f484b8d659fed6a2b1733b46ae6
     or http://127.0.0.1:8888/?token=c4b7784d784206fc357b8f484b8d659fed6a2b1733b46ae6

Вы можете видеть, что вывод происходит, но не распознается sp.stdout.readline().

Как правильно транслировать из sp.stdout?


Следуя подсказке @Douglas Myers-Turnbull, я изменил свою основную функцию на:

if __name__ == "__main__":

    own_ip = get_own_ip()
    # store ip as byte stream
    own_ip_bstr = own_ip.encode()

    sp = subprocess.Popen(["jupyter-notebook"
                           , "--ip='%s'" % own_ip
                           , "--port=%i" % port
                           , "--no-browser"],
                          stderr=subprocess.PIPE,
                          stdin=subprocess.PIPE,
                          bufsize=1)

    # set up handler to terminate jupyter
    signal.signal(signal.SIGINT, signal_handler)

    with open('jupyter.log', mode='wb') as flog:
        for line in sp.stderr:
            flog.write(line)
            if own_ip_bstr in line.strip():
                with open('jupyter.url', mode='w') as furl:
                    furl.write(line.decode().split('NotebookApp] ')[1])
                break

        for line in sp.stderr:
            flog.write(line)

1 Ответ

3 голосов
/ 06 мая 2020

Вместо этого вам нужно захватить stderr!

Я думаю, что эти сообщения пишутся на stderr, а не на stdout. Поэтому вам нужно вместо этого использовать sp.stderr. Это типично для структуры python logging.

Вы можете проверить, что это так, запустив это в своей оболочке (если вы используете Linux):

jupyter notebook > stdout.log 2> stderr.log

Если буфер начинает заполняться ...

Вероятно, вы не столкнетесь с этой проблемой при выводе только из записной книжки jupyter, но я ранее сталкивался с ошибкой, когда буфер вывода заполнялся до моего вызова код мог это обработать. Вам нужно убедиться, что ваш код обрабатывает строки из stdout (и / или stderr) по крайней мере так же быстро, как jupyter notebook пишет строки. Если это не так, вы можете обработать строки, поместив их в очереди. Примерно так:

    def _reader(cls, pipe_type, pipe, queue):
        """Read in lines of text (utf-8) and add them into the queue."""
        try:
            with pipe:
                for line in iter(pipe.readline, b""):
                    queue.put((pipe_type, line))
        finally:
            queue.put(None)
#
    def stream_cmd(log_callback):
        """Stream lines of stdout and stderr into a queue, then call log_callback on them.
        By putting the lines into a queue and processing with log_callback on another thread, it's ok if log_callback takes a bit longer than the output.
        """
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, bufsize=1)
        try:
            q = Queue()
            Thread(target=_reader, args=[1, p.stdout, q]).start()
            Thread(target=_reader, args=[2, p.stderr, q]).start()
            for _ in range(2):
                for source, line in iter(q.get, None):
                    log_callback(source, line)
            exit_code = p.wait(timeout=timeout_secs)
        finally:
            p.kill()
        if exit_code != 0:
            raise subprocess.CalledProcessError(
                exit_code, " ".join(cmd), "<<unknown>>", "<<unknown>>"
            )

Раньше я успешно использовал аналогичный код, но в нем могут быть ошибки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...