使用管道将打印到STDERR的内容从Jupy捕获到Python变量中

2024-10-01 00:34:12 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图捕捉在jupyter笔记本中运行命令时打印到STDERR的内容。特别是,我使用的是TensorFlow,它来自C部分的fprintf,通常在控制台上打印,但我想保存到Python变量中。在

我一直在使用来自IPython codebase的FDRedirector,它设置os.pipe将输出捕获到Python字符串中。在

然而,这段代码的问题是它挂起内核以获得足够大的输出。我怀疑它在输出超过65k时会挂起,因为这是Linux上的管道缓冲区大小,gdb显示挂起发生在write中。有没有人有一个解决方案可以处理更大的输出?在

{我现在做什么

STDERR = 2
redirect = FDRedirector(STDERR)
import tensorflow as tf
sess = tf.Session("")
node = tf.Print(tf.constant(1), [tf.constant(1)], "longstringlongstring")
def print_to_stderr():
    sess.run(node)   # this prints to stderr
redirect.start();
print_to_stderr()
captured_stderr = redirect.stop()

最后,“captured\u stderr”包含打印到stderr的所有内容,包括longstringlongstring。如果您使longstring部件更长(大于100k),则此部件将冻结。在


Tags: tonode内容部件tfstderrjupyterredirect
3条回答

希望这对你有用

import logging, sys
from StringIO import StringIO

def get_stderr():
    saved_stderr = sys.stderr
    stderr_string_io = StringIO()

    for handler in logging.root.handlers:
        if handler.stream is sys.stderr:
            handler.stream = stderr_string_io

    sys.stderr = stderr_string_io

    try:
        logging.error("Foobar!!!")

    finally:
        # set the stdout and stderr back to their original values
        for handler in logging.root.handlers:
            if handler.stream is sys.stderr:
                handler.stream = saved_stderr

        sys.stderr = saved_stderr

    err_output = stderr_string_io.getvalue()
    return err_output

for x in [1, 2]:
    err_output = get_stderr()
    print  "Run %d: %s" % (x, err_output)

您可以尝试将输出通过管道传输到临时文件,这样就没有缓冲区限制:

STDERR=2
STDOUT=1
import os
import sys
import tempfile

class captured:
    def __init__(self, fd=STDERR):
        self.fd = fd
        self.prevfd = None

    def __enter__(self):
        t = tempfile.NamedTemporaryFile()
        print 'Piping your output to ' + t.name
        self.prevfd = os.dup(self.fd)
        os.dup2(t.fileno(), self.fd)
        return t

    def __exit__(self, exc_type, exc_value, traceback):
        os.dup2(self.prevfd, self.fd)

with captured(fd=STDOUT) as tmp:
    os.system('cat 1mbfile.txt');

print "Captured:", open(tmp.name).read()    

请告诉我这是否对你有用。不幸的是我没有安装TF。在

Jupyter本身在向一个小区输出1Mb数据时幸存了下来:)

FDRedirector的问题是,它只从管道读取一次。由于管道的生产者端和使用者端都处于同一进程中,一旦缓冲区已满,这将阻塞管道的写入端。解决方法是从读取端连续读取,而不阻塞另一端。一种方法是生成一个消费者线程。在

这里有一段代码可以做到这一点:将stderr重定向到管道并使用线程连续读取。在

更新:正如OP的评论所建议的,代码现在是针对python3的。假设您希望捕获的输出是python3字符串(即Unicode字符串),现在,redirect将读取的字节转换为字符串。为此,它接受encodingerrors参数——就像Python的decode(并使用相同的默认值)。这解决了一般用例的一个实际问题:如果您想将捕获的数据作为一个字符串进行进一步处理,那么您必须知道stderr是以哪种编码方式写入的。对于另一个答案中的方法也是如此,其中流被重定向到一个文件。{6}你可以用cd6}来修改代码。在

import os, sys, threading
from contextlib import contextmanager
from io import StringIO

STDOUT, STDERR = 1, 2    

@contextmanager
def redirect(fd, encoding="utf-8", errors="strict"):
    # Save original handle so we can restore it later.
    saved_handle = os.dup(fd)

    # Redirect `fd` to the write end of the pipe.
    pipe_read, pipe_write = os.pipe()
    os.dup2(pipe_write, fd)
    os.close(pipe_write)

    # This thread reads from the read end of the pipe.
    def consumer_thread(f, data):
        while True:
            buf = os.read(f, 1024)
            if not buf: break
            data.write(buf.decode(encoding, errors))
        os.close(f)
        return

    # Spawn consumer thread, and give it a mutable `data` item to
    # store the redirected output.
    data = StringIO()
    thread = threading.Thread(target = consumer_thread, args=(pipe_read, data))
    thread.start()

    yield data

    # Cleanup: flush streams, restore `fd`
    { STDERR: sys.stderr, STDOUT: sys.stdout}[fd].flush()
    os.dup2(saved_handle, fd)
    os.close(saved_handle)
    thread.join()

然后,redirect上下文管理器可用于临时将任何流重定向到内部缓冲区。注意,在Jupyter下,sys.stderr是连接到STDERR而不是,它是一个ipykernel.iostream.OutStream对象,模拟Pythonfile。所以我们必须os.write到{}才能看到重定向工作。在

^{pr2}$

相关问题 更多 >