来自Jupyter笔记本中Python子进程的实时stdout输出

时间:2016-07-27 14:36:36

标签: python subprocess ipython stdout jupyter

我正在使用子进程从Python(3.5.2)脚本运行命令行程序,我在Jupyter笔记本中运行该脚本。子进程需要很长时间才能运行,因此我希望它的stdout能够在Jupyter笔记本中实时打印到屏幕上。

我可以在从终端运行的普通Python脚本中做到这一点没问题。我是这样做的:

def run_command(cmd):
from subprocess import Popen, PIPE
import shlex

with Popen(shlex.split(cmd), stdout=PIPE, bufsize=1, universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='')
    exit_code = p.poll()
return exit_code

但是,当我在Jupyter笔记本中运行脚本时,它不会将stdout实时打印到屏幕上。相反,它会在子进程运行完毕后打印所有内容。

有没有人对如何解决这个问题有任何想法?

非常感谢, 约翰尼

4 个答案:

答案 0 :(得分:10)

ipython笔记本拥有它自己的support for running shell commands。如果您不需要使用子流程捕获,则可以执行

cmd = 'ls -l'
!{cmd}

执行命令的输出!通过笔记本自动传送。

答案 1 :(得分:2)

如果设置stdout = None(这是默认设置,因此您可以完全省略stdout参数),那么您的进程应将其输出写入运行IPython笔记本服务器的终端。

这是因为默认行为是子进程从父文件处理程序继承(参见docs)。

您的代码如下所示:

from subprocess import Popen, PIPE
import shlex

def run_command(cmd):
    p = Popen(shlex.split(cmd), bufsize=1, universal_newlines=True)
    return p.poll()

这不会在浏览器中打印到笔记本中,但至少在其他代码运行时,您将能够异步查看子进程的输出。

希望这有帮助。

答案 2 :(得分:0)

带有标准输出和标准错误的Jupyter嘲笑。这样应该可以得到所需的信息,并在命令启动失败时为您提供一个更有用的异常。

import signal
import subprocess as sp


class VerboseCalledProcessError(sp.CalledProcessError):
    def __str__(self):
        if self.returncode and self.returncode < 0:
            try:
                msg = "Command '%s' died with %r." % (
                    self.cmd, signal.Signals(-self.returncode))
            except ValueError:
                msg = "Command '%s' died with unknown signal %d." % (
                    self.cmd, -self.returncode)
        else:
            msg = "Command '%s' returned non-zero exit status %d." % (
                self.cmd, self.returncode)

        return f'{msg}\n' \
               f'Stdout:\n' \
               f'{self.output}\n' \
               f'Stderr:\n' \
               f'{self.stderr}'


def bash(cmd, print_stdout=True, print_stderr=True):
    proc = sp.Popen(cmd, stderr=sp.PIPE, stdout=sp.PIPE, shell=True, universal_newlines=True,
                    executable='/bin/bash')

    all_stdout = []
    all_stderr = []
    while proc.poll() is None:
        for stdout_line in proc.stdout:
            if stdout_line != '':
                if print_stdout:
                    print(stdout_line, end='')
                all_stdout.append(stdout_line)
        for stderr_line in proc.stderr:
            if stderr_line != '':
                if print_stderr:
                    print(stderr_line, end='', file=sys.stderr)
                all_stderr.append(stderr_line)

    stdout_text = ''.join(all_stdout)
    stderr_text = ''.join(all_stderr)
    if proc.wait() != 0:
        raise VerboseCalledProcessError(proc.returncode, cmd, stdout_text, stderr_text)

答案 3 :(得分:0)

用显式from subprocess import Popen, PIPE import shlex def run_command(cmd): with Popen(shlex.split(cmd), stdout=PIPE, bufsize=1, universal_newlines=True) as p: while True: line = p.stdout.readline() if not line: break print(line) exit_code = p.poll() return exit_code 调用替换for循环对我来说很有效。

func main() {
    t := time.Now()
    callme := func() {
        // do somethign more
        fmt.Println("callme", time.Since(t))
    }
    ticker := time.NewTicker(10 * time.Second)
    first := make(chan bool, 1)
    first <- true
    for {
        select {
        case <-ticker.C:
            callme()
        case <-first:
            callme()
        }
        t = time.Now()
    }
    close(first)
}

即使是4年后,关于迭代器的问题仍然存在。