Suppress stdout / stderr print from Python functions
我有一个Python脚本,它使用我的雇主提供的一些闭箱式Python函数(即我无法编辑这些函数)。当我调用这些函数时,它们正在打印输出到我想要压制的linux终端。我尝试过重定向stdout / stderr;
1 2 3 4 | orig_out = sys.stdout sys.stdout = StringIO() rogue_function() sys.stdout = orig_out |
但这没有抓住输出。我认为我通过Python调用的函数(上面的rogue_function())实际上是编译C代码的包装器,它们实际上正在进行打印。
有没有人知道我可以通过函数(以及函数调用的任何子函数)对stdout / stderr的任何打印进行"深度捕获"?
更新:
我最终采用了下面选定答案中概述的方法并编写了一个上下文管理器来压制stdout和stderr:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | # 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) |
要使用它你只需:
1 2 | with suppress_stdout_stderr(): rogue_function() |
这工作"非常好"。它确实抑制了使我的脚本混乱的流氓功能的打印输出。我在测试它时注意到它允许通过凸起的异常以及一些记录器打印,我不完全清楚为什么。我认为这与将这些消息发送到stdout / stderr有关(我认为它发生在我的上下文管理器退出之后)。如果有人能证实这一点,我有兴趣听到细节......
这种方法(通过相关侧栏找到)可能有效。它在sys.stdout等中重新分配文件描述符而不仅仅是包装器。
从python 3.5开始,我们可以使用
1 2 3 4 5 6 7 8 9 | 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) |
请注意,抑制
1 2 3 4 5 6 7 8 9 10 | 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() |
当运行上面只打印
1 2 3 4 5 6 | 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' |
到终点站。永远不会忽视未处理的异常。
您是否尝试重定向stderr?
例如
1 2 3 4 5 | 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)。
我的解决方案与您的解决方案类似但使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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() |
我创建此文章的背景是在这篇博客文章中。我认为与你的相似。
我在
1 2 3 | with stdchannel_redirected(sys.stderr, os.devnull): if compiler.has_function('clock_gettime', libraries=['rt']): libraries.append('rt') |
我为此使用装饰器。它保存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 |
使用:
1 2 3 | @suppress_std def function_std_suppressed(): # code here |
OP并没有真正要求,但我需要隐藏和存储输出,并且如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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]) |
用法:
1 2 3 | with Hider() as h: spammy_function() result = h.autopsy() |
(仅使用Python 3测试)
编辑:现在允许选择
python 3.6工作版,经过百万次抑制测试,没有任何错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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() |
如果您在基于Linux的计算机上运行此脚本,您应该能够:
1 | $> ./runscript.py > output.txt |