在Python中静音函数的stdout而不影响sys.stdout并且无需恢复每次调用

81 投票
8 回答
84112 浏览
提问于 2025-04-15 22:40

有没有办法在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 个回答

24

我来得有点晚,但我觉得我找到了一个更简单的解决办法。

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

补充一下其他人说的内容,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

123

你这样给 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 实现是一样的。我觉得没有比这更简洁或更快的了;-)。

撰写回答