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

jer*_*dha 32 python stdout

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

orig_out = sys.stdout
sys.stdout = StringIO()
rogue_function()
sys.stdout = orig_out
Run Code Online (Sandbox Code Playgroud)

但这没有抓住输出.我认为我通过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)
Run Code Online (Sandbox Code Playgroud)

要使用它你只需:

with suppress_stdout_stderr():
    rogue_function()
Run Code Online (Sandbox Code Playgroud)

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

And*_*eak 17

从 python 3.5 开始,我们可以使用内置的 in contextlib,即redirect_stdout和,以最少的工作来做到这一点redirect_stderr。我们只需要在我们的自定义上下文管理器中组合这两个内置上下文管理器,这可以使用Martijn 的答案中的 nice 模式轻松完成。将两个输出重定向到os.devnull应该足够安全和​​便携。

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)
Run Code Online (Sandbox Code Playgroud)

请注意,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()
Run Code Online (Sandbox Code Playgroud)

当运行上面只打印

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'
Run Code Online (Sandbox Code Playgroud)

到终端。未处理的异常不应该被忽视。


ipe*_*rov 7

python 3.6工作版本,经过百万次抑制测试,没有任何错误

import os
import sys

class suppress_stdout_stderr(object):
    def __enter__(self):
        self.outnull_file = open(os.devnull, 'w')
        self.errnull_file = open(os.devnull, 'w')

        self.old_stdout_fileno_undup    = sys.stdout.fileno()
        self.old_stderr_fileno_undup    = sys.stderr.fileno()

        self.old_stdout_fileno = os.dup ( sys.stdout.fileno() )
        self.old_stderr_fileno = os.dup ( sys.stderr.fileno() )

        self.old_stdout = sys.stdout
        self.old_stderr = sys.stderr

        os.dup2 ( self.outnull_file.fileno(), self.old_stdout_fileno_undup )
        os.dup2 ( self.errnull_file.fileno(), self.old_stderr_fileno_undup )

        sys.stdout = self.outnull_file        
        sys.stderr = self.errnull_file
        return self

    def __exit__(self, *_):        
        sys.stdout = self.old_stdout
        sys.stderr = self.old_stderr

        os.dup2 ( self.old_stdout_fileno, self.old_stdout_fileno_undup )
        os.dup2 ( self.old_stderr_fileno, self.old_stderr_fileno_undup )

        os.close ( self.old_stdout_fileno )
        os.close ( self.old_stderr_fileno )

        self.outnull_file.close()
        self.errnull_file.close()
Run Code Online (Sandbox Code Playgroud)

  • 我发现这比投票最高的答案更可靠。投票最高的答案并没有抑制来自底层 c++ 和 c 库的 stdout 和 stderr。这做到了。 (2认同)

Dou*_*gal 6

这种方法(通过相关侧栏找到)可能有效.它在sys.stdout等中重新分配文件描述符而不仅仅是包装器.