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

问题描述 投票:41回答:8

有没有一种方法在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

这种方式有效,但似乎非常低效。一定有更好的方法。有任何想法吗?

python stdout
8个回答
77
投票

正如你所做的那样,分配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

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

with nostdout():
    foo()

更多优化:您只需要将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

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


17
投票

为了补充其他人已经说过的内容,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]: 

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

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

在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]: 

你甚至可以使target完全可选:

def f(target=sys.stdout):
    # Here goes the function definition

注意,你需要

from __future__ import print_function

在Python 2中。


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

11
投票

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

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

用法:

with Suppressor():
    DoMyFunction(*args,**kwargs)

5
投票

Alex Martelli's answer略有修改......

这解决了您总是想要为函数抑制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)

2
投票

通过概括甚至更多,你可以得到一个很好的装饰器,可以捕获输出,甚至返回它:

import sys
import cStringIO
from functools import wraps

def mute(returns_output=False):
    """
        Decorate a function that prints to stdout, intercepting the output.
        If "returns_output" is True, the function will return a generator
        yielding the printed lines instead of the return values.

        The decorator litterally hijack sys.stdout during each function
        execution for ALL THE THREADS, so be careful with what you apply it to
        and in which context.

        >>> def numbers():
            print "42"
            print "1984"
        ...
        >>> numbers()
        42
        1984
        >>> mute()(numbers)()
        >>> list(mute(True)(numbers)())
        ['42', '1984']

    """

    def decorator(func):

        @wraps(func)
        def wrapper(*args, **kwargs):

            saved_stdout = sys.stdout
            sys.stdout = cStringIO.StringIO()

            try:
                out = func(*args, **kwargs)
                if returns_output:
                    out = sys.stdout.getvalue().strip().split()
            finally:
                sys.stdout = saved_stdout

            return out

        return wrapper

    return decorator

2
投票

我不认为它比这更清洁或更快;-)

呸!我想我可以做得更好一点:-D

import contextlib, cStringIO, sys

@contextlib.contextmanager
def nostdout():

    '''Prevent print to stdout, but if there was an error then catch it and
    print the output before raising the error.'''

    saved_stdout = sys.stdout
    sys.stdout = cStringIO.StringIO()
    try:
        yield
    except Exception:
        saved_output = sys.stdout
        sys.stdout = saved_stdout
        print saved_output.getvalue()
        raise
    sys.stdout = saved_stdout

哪个得到我原来想要的,正常抑制输出但是如果抛出错误则显示被抑制的输出。


1
投票

自python 3.4以来,redirect_stdout()已添加到contextlib中

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

import contextlib
import io

with contextlib.redirect_stdout(io.StringIO()):
    foo()
© www.soinside.com 2019 - 2024. All rights reserved.