在Python中静音函数的stdout而不影响sys.stdout并且无需恢复每次调用
有没有办法在Python中让标准输出(stdout)静音,而不需要像下面这样包裹一个函数调用呢?
原来的错误代码:
from sys import stdout
from copy import copy
save_stdout = copy(stdout)
stdout = open('trash','w')
foo()
stdout = save_stdout
编辑:这是Alex Martelli修正后的代码
import sys
save_stdout = sys.stdout
sys.stdout = open('trash', 'w')
foo()
sys.stdout = save_stdout
这个方法可以用,但看起来效率非常低。肯定还有更好的办法。有什么想法吗?
8 个回答
我来得有点晚,但我觉得我找到了一个更简单的解决办法。
import sys, traceback
class Suppressor():
def __enter__(self):
self.stdout = sys.stdout
sys.stdout = self
def __exit__(self, exception_type, value, traceback):
sys.stdout = self.stdout
if exception_type is not None:
# Do normal exception handling
raise Exception(f"Got exception: {exception_type} {value} {traceback}")
def write(self, x): pass
def flush(self): pass
用法:
with Suppressor():
DoMyFunction(*args,**kwargs)
补充一下其他人说的内容,Python 3.4 引入了一个叫做 contextlib.redirect_stdout
的上下文管理器。这个管理器可以接受一个文件(或类似文件的对象),把输出重定向到这个文件上。
如果把输出重定向到 /dev/null,那么输出就会被抑制,也就是说不会显示出来:
In [11]: def f(): print('noise')
In [12]: import os, contextlib
In [13]: with open(os.devnull, 'w') as devnull:
....: with contextlib.redirect_stdout(devnull):
....: f()
....:
In [14]:
如果被包裹的代码没有直接写入 sys.stdout
,你可以使用更简单的方法:
In [15]: with contextlib.redirect_stdout(None):
....: f()
....:
In [16]:
这个解决方案还可以改造成装饰器的形式使用:
import os, contextlib
def supress_stdout(func):
def wrapper(*a, **ka):
with open(os.devnull, 'w') as devnull:
with contextlib.redirect_stdout(devnull):
return func(*a, **ka)
return wrapper
@supress_stdout
def f():
print('noise')
f() # nothing is printed
还有一个可能的解决方案,偶尔也很有用,它在 Python 2 和 3 中都能使用,就是把 /dev/null 作为参数传给 f
,然后通过 print
函数的 file
参数来重定向输出:
In [14]: def f(target): print('noise', file=target)
In [15]: with open(os.devnull, 'w') as devnull:
....: f(target=devnull)
....:
In [16]:
你甚至可以让 target
完全变成可选的:
def f(target=sys.stdout):
# Here goes the function definition
注意,你需要在 Python 2 中这样做:
from __future__ import print_function
。
你这样给 stdout
变量赋值其实没有任何效果,假设 foo
里有 print
语句。这又是一个例子,说明为什么你不应该从模块内部导入东西(就像你现在这样),而是应该整体导入模块(然后使用带有模块名的名称)。顺便说一下,copy
这个东西是无关紧要的。你代码片段的正确写法是:
import sys
save_stdout = sys.stdout
sys.stdout = open('trash', 'w')
foo()
sys.stdout = save_stdout
现在,当代码正确时,可以考虑让它更优雅或更快。例如,你可以使用一个内存中的类文件对象,而不是文件 'trash':
import sys
import io
save_stdout = sys.stdout
sys.stdout = io.BytesIO()
foo()
sys.stdout = save_stdout
为了优雅,使用 上下文 是最好的,比如:
import contextlib
import io
import sys
@contextlib.contextmanager
def nostdout():
save_stdout = sys.stdout
sys.stdout = io.BytesIO()
yield
sys.stdout = save_stdout
一旦你定义了这个上下文,在你不想要 stdout
的任何代码块中,
with nostdout():
foo()
更多的优化:你只需要把 sys.stdout 替换成一个有无操作 write
方法的对象。例如:
import contextlib
import sys
class DummyFile(object):
def write(self, x): pass
@contextlib.contextmanager
def nostdout():
save_stdout = sys.stdout
sys.stdout = DummyFile()
yield
sys.stdout = save_stdout
这样使用和之前的 nostdout
实现是一样的。我觉得没有比这更简洁或更快的了;-)。