在Python中使函数的stdout静音而不会破坏sys.stdout并恢复每个函数调用

Tim*_*ton 46 python stdout

有没有办法让Python在没有包含像下面这样的函数调用的情况下使stdout静音?

原始破码:

from sys import stdout
from copy import copy
save_stdout = copy(stdout)
stdout = open('trash','w')
foo()
stdout = save_stdout
Run Code Online (Sandbox Code Playgroud)

编辑:更正了Alex Martelli的代码

import sys
save_stdout = sys.stdout
sys.stdout = open('trash', 'w')
foo()
sys.stdout = save_stdout
Run Code Online (Sandbox Code Playgroud)

这种方式有效,但似乎非常低效.有是一个更好的办法.有任何想法吗?

Ale*_*lli 83

stdout正如你所做的那样分配变量没有任何效果,假设foo包含print语句 - 这是为什么你永远不应该从模块内部导入东西的另一个例子(就像你在这里做的那样),但总是一个模块作为一个整体(然后使用合格的名字).copy顺便说一句,这是无关紧要的.您的代码段的正确等效项是:

import sys
save_stdout = sys.stdout
sys.stdout = open('trash', 'w')
foo()
sys.stdout = save_stdout
Run Code Online (Sandbox Code Playgroud)

现在,当代码正确时,是时候让它更优雅或更快.例如,您可以使用内存中类文件对象而不是文件'trash':

import sys
import io
save_stdout = sys.stdout
sys.stdout = io.BytesIO()
foo()
sys.stdout = save_stdout
Run Code Online (Sandbox Code Playgroud)

对于优雅,上下文是最好的,例如:

import contextlib
import io
import sys

@contextlib.contextmanager
def nostdout():
    save_stdout = sys.stdout
    sys.stdout = io.BytesIO()
    yield
    sys.stdout = save_stdout
Run Code Online (Sandbox Code Playgroud)

一旦你定义了这个上下文,对于你不想要一个标准输出的任何一个块,

with nostdout():
    foo()
Run Code Online (Sandbox Code Playgroud)

更多优化:您只需要将sys.stdout替换为具有no-op 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
Run Code Online (Sandbox Code Playgroud)

使用方式与之前的实现方式相同nostdout.我不认为它比这更清洁或更快;-).

  • 应该注意的是,如果在线程环境中使用它,则替换将应用于*all*threads.因此,如果您在线程网络服务器中使用它,stdout将被所有线程删除(在该函数调用的持续时间内,这将是其他线程中的一些随机时间块).处理这种情况很难,并且涉及`threading.local`(大多数我建议尝试避免线程和stdout重定向). (12认同)
  • 我对此表示反对,因为它会使代码挂起 - 据我所知,如果在上下文中引发异常,代码就无法被中断。我认为 [@bitmous 的答案](http://stackoverflow.com/a/40054132/1194883) 更健壮*并且*以一种非常聪明的方式更优雅(不需要“DummyFile”类)。 (2认同)
  • 2022 年不适用于 Py3.10 - BytesIO.write 需要 byes-like 而不是 str。 (2认同)

vau*_*tah 21

为了补充其他人已经说过的内容,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]: 
Run Code Online (Sandbox Code Playgroud)

此解决方案可以适用于装饰器:

import os, contextlib

def supress_stdout(func):
    def wrapper(*a, **ka):
        with open(os.devnull, 'w') as devnull:
            with contextlib.redirect_stdout(devnull):
                func(*a, **ka)
    return wrapper

@supress_stdout
def f():
    print('noise')

f() # nothing is printed
Run Code Online (Sandbox Code Playgroud)


在Python 2和3中都可以使用的另一个可能且偶尔有用的解决方案是将/ dev/null作为参数传递给,f并使用函数的file参数重定向输出print:

In [14]: def f(target): print('noise', file=target)

In [15]: with open(os.devnull, 'w') as devnull:
   ....:     f(target=devnull)
   ....:     

In [16]: 
Run Code Online (Sandbox Code Playgroud)

你甚至可以target完全选择:

def f(target=sys.stdout):
    # Here goes the function definition
Run Code Online (Sandbox Code Playgroud)

注意,你需要

from __future__ import print_function
Run Code Online (Sandbox Code Playgroud)

在Python 2中.

  • 适用于Python 3.4及更高版本的最优雅的解决方案。 (2认同)

Phi*_*ipp 15

为什么你认为这是低效的?你测试过吗?顺便说一下,它根本不起作用,因为你正在使用该from ... import语句.替换sys.stdout很好,但不要复制,也不要使用临时文件.改为打开null设备:

import sys
import os

def foo():
    print "abc"

old_stdout = sys.stdout
sys.stdout = open(os.devnull, "w")
try:
    foo()
finally:
    sys.stdout.close()
    sys.stdout = old_stdout
Run Code Online (Sandbox Code Playgroud)

  • 它应该是'open(os.devnull,'w')`.通常,如果需要*real*file对象,`os.devnull`很有用.但是`print`接受任何带有`write()`方法的东西. (4认同)

小智 13

我觉得这个问题是一个更清洁的解决方案.

import sys, traceback

class Suppressor(object):

    def __enter__(self):
        self.stdout = sys.stdout
        sys.stdout = self

    def __exit__(self, type, value, traceback):
        sys.stdout = self.stdout
        if type is not None:
            # Do normal exception handling

    def write(self, x): pass
Run Code Online (Sandbox Code Playgroud)

用法:

with Suppressor():
    DoMyFunction(*args,**kwargs)
Run Code Online (Sandbox Code Playgroud)

  • 在这里进行正常的异常处理是什么意思?这究竟会抓住什么?如果我想将它用于任意数量的抽象案例,我应该期待什么点击“如果类型不是无”,如果我省略那行会发生什么? (2认同)

Pin*_*en 9

从 python 3.4 开始,redirect_stdout()已经被添加到 contextlib 中

对于 python >= 3.4,应该这样做:

import contextlib
import io

with contextlib.redirect_stdout(io.StringIO()):
    foo()
Run Code Online (Sandbox Code Playgroud)

  • 这是上面@vaultah 答案的重复吗? (2认同)

tgr*_*ray 5

Alex Martelli的回答略有修改......

这解决了您总是希望抑制stdout函数而不是单独调用函数的情况.

如果foo()多次调用它可能更好/更容易包装函数(装饰它).这样您就可以更改foo一次的定义,而不是在with语句中包含函数的每次使用.

import sys
from somemodule import foo

class DummyFile(object):
    def write(self, x): pass

def nostdout(func):
    def wrapper(*args, **kwargs):        
        save_stdout = sys.stdout
        sys.stdout = DummyFile()
        func(*args, **kwargs)
        sys.stdout = save_stdout
    return wrapper

foo = nostdout(foo)
Run Code Online (Sandbox Code Playgroud)