简体   繁体   English

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

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

I'm using a Python library that does something to an object我正在使用一个对对象执行某些操作的 Python 库

do_something(my_object)

and changes it.并改变它。 While doing so, it prints some statistics to stdout, and I'd like to get a grip on this information.这样做时,它会将一些统计信息打印到标准输出,我想掌握这些信息。 The proper solution would be to change do_something() to return the relevant information,正确的解决方案是更改do_something()以返回相关信息,

out = do_something(my_object)

but it will be a while before the devs of do_something() get to this issue.但是在do_something()的开发人员解决这个问题之前还需要一段时间。 As a workaround, I thought about parsing whatever do_something() writes to stdout.作为一种解决方法,我考虑过解析任何do_something()写入标准输出的内容。

How can I capture stdout output between two points in the code, eg,如何捕获代码中两点之间的标准输出输出,例如,

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

? ?

Try this context manager:试试这个上下文管理器:

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

Usage:用法:

with Capturing() as output:
    do_something(my_object)

output is now a list containing the lines printed by the function call. output现在是一个包含函数调用打印的行的列表。

Advanced usage:高级用法:

What may not be obvious is that this can be done more than once and the results concatenated:可能不明显的是,这可以多次完成并将结果连接起来:

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)

Output:输出:

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

Update : They added redirect_stdout() to contextlib in Python 3.4 (along with redirect_stderr() ).更新:他们将redirect_stdout()添加到 Python 3.4 中的contextlib (以及redirect_stderr() )。 So you could use io.StringIO with that to achieve a similar result (though Capturing being a list as well as a context manager is arguably more convenient).所以你可以使用io.StringIO来实现类似的结果(尽管Capturing是一个列表以及一个上下文管理器可以说更方便)。

In python >= 3.4, contextlib contains a redirect_stdout decorator.在 python >= 3.4 中,contextlib 包含一个redirect_stdout装饰器。 It can be used to answer your question like so:它可以用来回答您的问题,如下所示:

import io
from contextlib import redirect_stdout

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

From the docs :文档

Context manager for temporarily redirecting sys.stdout to another file or file-like object.用于临时将 sys.stdout 重定向到另一个文件或类文件对象的上下文管理器。

This tool adds flexibility to existing functions or classes whose output is hardwired to stdout.此工具为输出硬连线到标准输出的现有函数或类增加了灵活性。

For example, the output of help() normally is sent to sys.stdout.例如,help() 的输出通常发送到 sys.stdout。 You can capture that output in a string by redirecting the output to an io.StringIO object:您可以通过将输出重定向到 io.StringIO 对象来捕获字符串中的输出:

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

To send the output of help() to a file on disk, redirect the output to a regular file:要将 help() 的输出发送到磁盘上的文件,请将输出重定向到常规文件:

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

To send the output of help() to sys.stderr:将 help() 的输出发送到 sys.stderr:

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

Note that the global side effect on sys.stdout means that this context manager is not suitable for use in library code and most threaded applications.请注意,对 sys.stdout 的全局副作用意味着此上下文管理器不适合在库代码和大多数线程应用程序中使用。 It also has no effect on the output of subprocesses.它也对子流程的输出没有影响。 However, it is still a useful approach for many utility scripts.但是,对于许多实用程序脚本来说,它仍然是一种有用的方法。

This context manager is reentrant.这个上下文管理器是可重入的。

Here is an async solution using file pipes.这是使用文件管道的异步解决方案。

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

Example usage:用法示例:

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()

Based on kindall and ForeverWintr 's answer.基于kindallForeverWintr的回答。

I create redirect_stdout function for Python<3.4 :我为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()

Also drawing on @kindall and @ForeveWintr's answers, here's a class that accomplishes this.还借鉴了@kindall 和@ForeveWintr 的答案,这是一个完成此任务的类。 The main difference from previous answers is that this captures it as a string , not as a StringIO object, which is much more convenient to work with!与先前答案的主要区别在于,这将其捕获为 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

Examples:例子:

# 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)

This is implemented in Sciris as sc.capture() .这在Sciris中实现为sc.capture()

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

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