繁体   English   中英

如何从 Python 函数调用中捕获标准输出输出?

[英]How to capture stdout output from a Python function call?

我正在使用一个对对象执行某些操作的 Python 库

do_something(my_object)

并改变它。 这样做时,它会将一些统计信息打印到标准输出,我想掌握这些信息。 正确的解决方案是更改do_something()以返回相关信息,

out = do_something(my_object)

但是在do_something()的开发人员解决这个问题之前还需要一段时间。 作为一种解决方法,我考虑过解析任何do_something()写入标准输出的内容。

如何捕获代码中两点之间的标准输出输出,例如,

start_capturing()
do_something(my_object)
out = end_capturing()

?

试试这个上下文管理器:

from io import StringIO 
import sys

class Capturing(list):
    def __enter__(self):
        self._stdout = sys.stdout
        sys.stdout = self._stringio = StringIO()
        return self
    def __exit__(self, *args):
        self.extend(self._stringio.getvalue().splitlines())
        del self._stringio    # free up some memory
        sys.stdout = self._stdout

用法:

with Capturing() as output:
    do_something(my_object)

output现在是一个包含函数调用打印的行的列表。

高级用法:

可能不明显的是,这可以多次完成并将结果连接起来:

with Capturing() as output:
    print('hello world')

print('displays on screen')

with Capturing(output) as output:  # note the constructor argument
    print('hello world2')

print('done')
print('output:', output)

输出:

displays on screen                     
done                                   
output: ['hello world', 'hello world2']

更新:他们将redirect_stdout()添加到 Python 3.4 中的contextlib (以及redirect_stderr() )。 所以你可以使用io.StringIO来实现类似的结果(尽管Capturing是一个列表以及一个上下文管理器可以说更方便)。

在 python >= 3.4 中,contextlib 包含一个redirect_stdout装饰器。 它可以用来回答您的问题,如下所示:

import io
from contextlib import redirect_stdout

f = io.StringIO()
with redirect_stdout(f):
    do_something(my_object)
out = f.getvalue()

文档

用于临时将 sys.stdout 重定向到另一个文件或类文件对象的上下文管理器。

此工具为输出硬连线到标准输出的现有函数或类增加了灵活性。

例如,help() 的输出通常发送到 sys.stdout。 您可以通过将输出重定向到 io.StringIO 对象来捕获字符串中的输出:

 f = io.StringIO() with redirect_stdout(f): help(pow) s = f.getvalue()

要将 help() 的输出发送到磁盘上的文件,请将输出重定向到常规文件:

 with open('help.txt', 'w') as f: with redirect_stdout(f): help(pow)

将 help() 的输出发送到 sys.stderr:

 with redirect_stdout(sys.stderr): help(pow)

请注意,对 sys.stdout 的全局副作用意味着此上下文管理器不适合在库代码和大多数线程应用程序中使用。 它也对子流程的输出没有影响。 但是,对于许多实用程序脚本来说,它仍然是一种有用的方法。

这个上下文管理器是可重入的。

这是使用文件管道的异步解决方案。

import threading
import sys
import os

class Capturing():
    def __init__(self):
        self._stdout = None
        self._stderr = None
        self._r = None
        self._w = None
        self._thread = None
        self._on_readline_cb = None

    def _handler(self):
        while not self._w.closed:
            try:
                while True:
                    line = self._r.readline()
                    if len(line) == 0: break
                    if self._on_readline_cb: self._on_readline_cb(line)
            except:
                break

    def print(self, s, end=""):
        print(s, file=self._stdout, end=end)

    def on_readline(self, callback):
        self._on_readline_cb = callback

    def start(self):
        self._stdout = sys.stdout
        self._stderr = sys.stderr
        r, w = os.pipe()
        r, w = os.fdopen(r, 'r'), os.fdopen(w, 'w', 1)
        self._r = r
        self._w = w
        sys.stdout = self._w
        sys.stderr = self._w
        self._thread = threading.Thread(target=self._handler)
        self._thread.start()

    def stop(self):
        self._w.close()
        if self._thread: self._thread.join()
        self._r.close()
        sys.stdout = self._stdout
        sys.stderr = self._stderr

用法示例:

from Capturing import *
import time

capturing = Capturing()

def on_read(line):
    # do something with the line
    capturing.print("got line: "+line)

capturing.on_readline(on_read)
capturing.start()
print("hello 1")
time.sleep(1)
print("hello 2")
time.sleep(1)
print("hello 3")
capturing.stop()

基于kindallForeverWintr的回答。

我为Python<3.4创建了redirect_stdout函数:

import io
from contextlib import contextmanager

@contextmanager
def redirect_stdout(f):
    try:
        _stdout = sys.stdout
        sys.stdout = f
        yield
    finally:
        sys.stdout = _stdout


f = io.StringIO()
with redirect_stdout(f):
    do_something()
out = f.getvalue()

还借鉴了@kindall 和@ForeveWintr 的答案,这是一个完成此任务的类。 与先前答案的主要区别在于,这将其捕获为 string ,而不是StringIO对象,使用起来更方便!

import io
from collections import UserString
from contextlib import redirect_stdout

class capture(UserString, str, redirect_stdout):
    '''
    Captures stdout (e.g., from ``print()``) as a variable.

    Based on ``contextlib.redirect_stdout``, but saves the user the trouble of
    defining and reading from an IO stream. Useful for testing the output of functions
    that are supposed to print certain output.
    '''

    def __init__(self, seq='', *args, **kwargs):
        self._io = io.StringIO()
        UserString.__init__(self, seq=seq, *args, **kwargs)
        redirect_stdout.__init__(self, self._io)
        return

    def __enter__(self, *args, **kwargs):
        redirect_stdout.__enter__(self, *args, **kwargs)
        return self

    def __exit__(self, *args, **kwargs):
        self.data += self._io.getvalue()
        redirect_stdout.__exit__(self, *args, **kwargs)
        return

    def start(self):
        self.__enter__()
        return self

    def stop(self):
        self.__exit__(None, None, None)
        return

例子:

# Using with...as
with capture() as txt1:
    print('Assign these lines')
    print('to a variable')

# Using start()...stop()
txt2 = capture().start()
print('This works')
print('the same way')
txt2.stop()

print('Saved in txt1:')
print(txt1)
print('Saved in txt2:')
print(txt2)

这在Sciris中实现为sc.capture()

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM