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

从Python函数中抑制stdout/stderr打印,python,stdout,Python,Stdout,我有一个Python脚本,它使用我的雇主提供的一些封闭式Python函数(即,我不能编辑这些函数)。当我调用这些函数时,它们将输出打印到我想要抑制的linux终端。我已经尝试通过重定向stdout/stderr orig_out = sys.stdout sys.stdout = StringIO() rogue_function() sys.stdout = orig_out 但这并没有抓住输出。我认为我通过Python调用的函数(上面的rogue_function())实际上是编译C代码的

我有一个Python脚本,它使用我的雇主提供的一些封闭式Python函数(即,我不能编辑这些函数)。当我调用这些函数时,它们将输出打印到我想要抑制的linux终端。我已经尝试通过重定向stdout/stderr

orig_out = sys.stdout
sys.stdout = StringIO()
rogue_function()
sys.stdout = orig_out
但这并没有抓住输出。我认为我通过Python调用的函数(上面的rogue_function())实际上是编译C代码的包装器,实际上是在进行打印

是否有人知道我可以通过一个函数(以及该函数调用的任何子函数)对提交给stdout/stderr的任何打印进行“深度捕获”的方法

更新

我最终采用了下面所选答案中概述的方法,并编写了一个上下文管理器来Superss 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有关(我认为它发生在我的上下文管理器退出之后)。如果有人能证实这一点,我很想听听细节

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

$> ./runscript.py > output.txt

你也试着重定向stderr了吗? e、 g


同时,使用StringIO可能会使用额外的内存。您可以使用虚拟设备代替(例如)。

(通过相关侧栏找到)可能会起作用。它在sys.stdout等中重新分配文件描述符,而不仅仅是包装器。

我的解决方案与您的解决方案类似,但使用
contextlib
,并且略短且易于理解(IMHO)

导入上下文库
@contextlib.contextmanager
def stdchannel_已重定向(stdchannel,dest_文件名):
"""
用于临时重定向stdout或stderr的上下文管理器
例如。:
重定向stdchannel_时(sys.stderr,os.devnull):
如果编译器.has_函数('clock_gettime',libraries=['rt']):
libraries.append('rt'))
"""
尝试:
oldstdchannel=os.dup(stdchannel.fileno())
dest_文件=打开(dest_文件名'w')
os.dup2(dest_file.fileno(),stdchannel.fileno())
产量
最后:
如果oldstdchannel不是None:
os.dup2(oldstdchannel,stdcchannel.fileno())
如果dest_文件不是None:
dest_file.close()文件
我为什么创建这个的背景是在。我想和你的差不多

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

stdchannel_重定向时的
(sys.stderr,os.devnull):
如果编译器.has_函数('clock_gettime',libraries=['rt']):
libraries.append('rt'))

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进行测试)


编辑:现在允许选择
stderr
stdout
或两者,如
Hider([stdout,stderr])
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()

从Python3.5开始,我们可以使用内置的和来完成这项工作。我们只需要将这两个内置的上下文管理器组合到我们的定制上下文管理器中,这可以很容易地使用。将两个输出重定向到应足够安全和便携

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'
回溯(最近一次呼叫最后一次):
文件“tmp.py”,第20行,在
流氓函数()
rogue_函数中第16行的文件“foo.py”
1+‘a’
TypeError:不支持+:“int”和“str”的操作数类型

到终点站。未处理的异常永远不会被忽略。

我为此使用了一个装饰程序。它保存
sys.stdout
sys.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
只需使用Linux/Unix即可:

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

(从相关侧栏)是否有效?您是否尝试将其设置为文件,而不是将
sys.stdout
设置为
StringIO()
?i、 e.
sys.stdout=open('log.txt','w')
Dougal,谢谢,看起来很有希望,我明天会试试。nullpointer,我试着将它定向到一个自定义的nullpointer()类,但也没有成功。@Dougal,谢谢,成功了!如果你愿意的话,把这个链接贴出来作为答案,我会选择它。请注意,我稍微编辑了这个片段,所以所有的文件描述符都在
\uuuuuuuuuuuu
中关闭了。在未关闭self.save_fds中的fd的情况下,此上下文管理器每次调用时都会泄漏两个文件描述符,导致在长时间运行的进程中耗尽文件描述符。我不想抑制脚本生成的所有输出,只抑制这些特定函数生成的虚假输出。否则,是的,这将是最简单的解决方案…这会有帮助:在这些特定函数之前和之后添加打印。使用正则表达式解析输出,以消除上面添加的打印之间的所有内容可能是C代码正在直接写入标准输出;你可能无法改变方向。抱歉。这些与Python3兼容吗?这应该是Python3可以接受的答案!还可以添加seld.\u stderr==sys.stderr;sys.stderr=self这对我来说是最简单的事情,谢谢!欢迎来到SO!如果你回答的老问题(这是一个超过8岁的问题)已经有了一个公认的答案(这里就是这种情况):请检查你是否真的
./myscript.py 2>/dev/null # gets rid of stderr
./myscript.py 2>/somewhere/myerror.log