从Python函数中抑制stdout / stderr打印

时间:2012-06-21 00:46:40

标签: python stdout

我有一个Python脚本,它使用了我的雇主提供的一些封闭式Python函数(即我无法编辑这些函数)。当我调用这些函数时,它们正在打印输出到我想要压制的linux终端。我尝试过重定向stdout / stderr;

orig_out = sys.stdout
sys.stdout = StringIO()
rogue_function()
sys.stdout = orig_out

但这无法捕获输出。我认为我通过Python调用的函数(上面的rogue_function())实际上是编译C代码的包装器,它们实际上正在进行打印。

有没有人知道我可以通过函数(以及函数调用的任何子函数)对stdout / stderr的任何打印进行“深度捕获”?

更新

我最终采用了下面选定答案中概述的方法并编写了一个上下文管理器来压制stdout和stderr:

# Define a context manager to suppress stdout and stderr.
class suppress_stdout_stderr(object):
    '''
    A context manager for doing a "deep suppression" of stdout and stderr in 
    Python, i.e. will suppress all print, even if the print originates in a 
    compiled C/Fortran sub-function.
       This will not suppress raised exceptions, since exceptions are printed
    to stderr just before a script exits, and after the context manager has
    exited (at least, I think that is why it lets exceptions through).      

    '''
    def __init__(self):
        # Open a pair of null files
        self.null_fds =  [os.open(os.devnull,os.O_RDWR) for x in range(2)]
        # Save the actual stdout (1) and stderr (2) file descriptors.
        self.save_fds = [os.dup(1), os.dup(2)]

    def __enter__(self):
        # Assign the null pointers to stdout and stderr.
        os.dup2(self.null_fds[0],1)
        os.dup2(self.null_fds[1],2)

    def __exit__(self, *_):
        # Re-assign the real stdout/stderr back to (1) and (2)
        os.dup2(self.save_fds[0],1)
        os.dup2(self.save_fds[1],2)
        # Close all file descriptors
        for fd in self.null_fds + self.save_fds:
            os.close(fd)

要使用它,您只需:

with suppress_stdout_stderr():
    rogue_function()

这非常好“。它确实抑制了使我的脚本混乱的流氓功能的打印输出。我在测试它时注意到它允许通过凸起的异常以及一些记录器打印,我不完全清楚为什么。我认为它与有关,当这些消息被发送到stdout / stderr时(我认为它发生在我的上下文管理器退出之后)。如果有人能证实这一点,我会有兴趣听取细节......

8 个答案:

答案 0 :(得分:6)

This approach(通过相关侧栏找到)可能有效。它重新分配文件描述符,而不仅仅是sys.stdout等中的包装器。

答案 1 :(得分:3)

从python 3.5开始,我们可以使用contextlibredirect_stdout中的内置函数以最少的工作量完成此任务。我们只需要在我们的自定义上下文管理器中组合这两个内置的上下文管理器,即可使用redirect_stderr轻松完成。将两个输出都重定向到the nice pattern in Martijn's answer here应该足够安全且可移植。

from contextlib import contextmanager,redirect_stderr,redirect_stdout
from os import devnull

@contextmanager
def suppress_stdout_stderr():
    """A context manager that redirects stdout and stderr to devnull"""
    with open(devnull, 'w') as fnull:
        with redirect_stderr(fnull) as err, redirect_stdout(fnull) as out:
            yield (err, out)

请注意,如果出现问题,抑制stderr仍会为您提供完整的追溯,这是一件好事:

import sys

def rogue_function():
    print('spam to stdout')
    print('important warning', file=sys.stderr)
    1 + 'a'
    return 42

with suppress_stdout_stderr():
    rogue_function()

运行以上命令时仅打印

Traceback (most recent call last):
  File "tmp.py", line 20, in <module>
    rogue_function()
  File "foo.py", line 16, in rogue_function
    1 + 'a'
TypeError: unsupported operand type(s) for +: 'int' and 'str'

到终端。未处理的异常永远不会被忽视。

答案 2 :(得分:2)

您是否尝试重定向stderr? e.g。

sys.stdout = StringIO()
sys.stderr = StringIO()
foo(bar)
sys.stdout = sys.__stdout__ # These are provided by python
sys.stderr = sys.__stderr__

同样使用StringIO可能会使用额外的内存。您可以使用虚拟设备(例如http://coreygoldberg.blogspot.com/2009/05/python-redirect-or-turn-off-stdout-and.html)。

答案 3 :(得分:2)

我的解决方案与您的解决方案类似,但使用contextlib并且更短且更容易理解(恕我直言)。

import contextlib


@contextlib.contextmanager
def stdchannel_redirected(stdchannel, dest_filename):
    """
    A context manager to temporarily redirect stdout or stderr

    e.g.:


    with stdchannel_redirected(sys.stderr, os.devnull):
        if compiler.has_function('clock_gettime', libraries=['rt']):
            libraries.append('rt')
    """

    try:
        oldstdchannel = os.dup(stdchannel.fileno())
        dest_file = open(dest_filename, 'w')
        os.dup2(dest_file.fileno(), stdchannel.fileno())

        yield
    finally:
        if oldstdchannel is not None:
            os.dup2(oldstdchannel, stdchannel.fileno())
        if dest_file is not None:
            dest_file.close()

我创建此内容的上下文位于this blog post。我认为与你的相似。

我在setup.py

中使用它
with stdchannel_redirected(sys.stderr, os.devnull):
    if compiler.has_function('clock_gettime', libraries=['rt']):
        libraries.append('rt')

答案 4 :(得分:1)

OP并没有真正要求,但我需要隐藏并存储输出,并且如下所示:

from io import StringIO
import sys

class Hider:
    def __init__(self, channels=('stdout',)):
        self._stomach = StringIO()
        self._orig = {ch : None for ch in channels}

    def __enter__(self):
        for ch in self._orig:
            self._orig[ch] = getattr(sys, ch)
            setattr(sys, ch, self)
        return self

    def write(self, string):
        self._stomach.write(string)

    def flush(self):
        pass

    def autopsy(self):
        return self._stomach.getvalue()

    def __exit__(self, *args):
        for ch in self._orig:
            setattr(sys, ch, self._orig[ch])

用法:

with Hider() as h:
    spammy_function()
    result = h.autopsy()

(仅使用Python 3测试)

编辑现在允许选择stderrstdout或两者,如Hider([stdout, stderr])

答案 5 :(得分:1)

我为此使用装饰器。它保存sys.stdoutsys.stderr引用,并使这些变量指向null。然后,在函数执行之后,将检索原始引用。重要的是要注意try / except块,即使在函数上引发异常时,该块也可以检索原始引用。

def suppress_std(func):
    def wrapper(*args, **kwargs):
        stderr_tmp = sys.stderr
        stdout_tmp = sys.stdout
        null = open(os.devnull, 'w')
        sys.stdout = null
        sys.stderr = null
        try:
            result = func(*args, **kwargs)
            sys.stderr = stderr_tmp
            sys.stdout = stdout_tmp
            return result
        except:
            sys.stderr = stderr_tmp
            sys.stdout = stdout_tmp
            raise
    return wrapper

要使用:

@suppress_std
def function_std_suppressed():
    # code here

答案 6 :(得分:0)

只需使用Linux / Unix:

./myscript.py 2>/dev/null # gets rid of stderr
./myscript.py 2>/somewhere/myerror.log

答案 7 :(得分:-2)

如果您在基于Linux的计算机上运行此脚本,您应该能够:

$> ./runscript.py > output.txt