48

我有一个 Python 脚本,它使用了我的雇主提供的一些封闭式 Python 函数(即我无法编辑这些函数)。当我调用这些函数时,它们会将输出打印到我想要​​抑制的 linux 终端。我尝试通过重定向标准输出/标准错误;

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 有关(我认为它发生在我的上下文管理器退出之后)。如果有人能证实这一点,我很想听听细节......

4

9 回答 9

23

从 python 3.5 开始,我们可以使用内置的.contextlibredirect_stdoutredirect_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)

请注意,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'

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

于 2018-09-21T11:02:34.363 回答
12

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

于 2012-06-21T22:00:12.967 回答
3

您是否也尝试重定向 stderr ?例如

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

于 2012-06-21T03:02:51.640 回答
3

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()
于 2018-05-20T18:32:24.197 回答
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()

我创建它的原因在此博客文章中。我觉得和你的差不多。

我在 a 中这样使用它setup.py

with stdchannel_redirected(sys.stderr, os.devnull):
    if compiler.has_function('clock_gettime', libraries=['rt']):
        libraries.append('rt')
于 2013-07-19T19:01:46.240 回答
2

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

于 2016-06-10T10:58:47.757 回答
1

我为此使用了装饰器。它保存sys.stdoutsys.stderr引用并使这些变量指向空值。然后,在函数执行后检索原始引用。重要的是要注意 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
于 2019-03-18T18:27:16.240 回答
0

只需使用 Linux/Unix:

./myscript.py 2>/dev/null # gets rid of stderr
./myscript.py 2>/somewhere/myerror.log
于 2020-10-10T23:24:55.540 回答
-2

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

$> ./runscript.py > output.txt
于 2012-06-21T02:45:25.387 回答