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

发布于 2024-09-01 19:28:40 字数 430 浏览 8 评论 0原文

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

这种方式有效,但似乎效率非常低。 必须有更好的方法。有什么想法吗?

Is there a way in Python to silence stdout without wrapping a function call like following?

Original Broken Code:

from sys import stdout
from copy import copy
save_stdout = copy(stdout)
stdout = open('trash','w')
foo()
stdout = save_stdout

Edit: Corrected code from Alex Martelli

import sys
save_stdout = sys.stdout
sys.stdout = open('trash', 'w')
foo()
sys.stdout = save_stdout

That way works but appears to be terribly inefficient. There has to be a better way. Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

小姐丶请自重 2024-09-08 19:28:40

假设 foo 包含 print 语句,按照您所做的那样分配 stdout 变量没有任何效果 - 这是为什么您永远不应该的另一个例子从模块内部导入内容(正如您在此处所做的那样),但始终将模块作为一个整体(然后使用限定名称)。顺便说一下,copy 是无关紧要的。您的代码片段的正确等效项是:

import sys
save_stdout = sys.stdout
sys.stdout = open('trash', 'w')
foo()
sys.stdout = save_stdout

现在,当代码正确时,就是让它变得更优雅或更快的时候了。例如,您可以使用内存中类似文件的对象来代替文件“垃圾”:

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 替换为具有无操作 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 实现相同的方式。我认为没有比这更干净或更快的了;-)。

Assigning the stdout variable as you're doing has no effect whatsoever, assuming foo contains print statements -- yet another example of why you should never import stuff from inside a module (as you're doing here), but always a module as a whole (then use qualified names). The copy is irrelevant, by the way. The correct equivalent of your snippet is:

import sys
save_stdout = sys.stdout
sys.stdout = open('trash', 'w')
foo()
sys.stdout = save_stdout

Now, when the code is correct, is the time to make it more elegant or fast. For example, you could use an in-memory file-like object instead of file 'trash':

import sys
import io
save_stdout = sys.stdout
sys.stdout = io.BytesIO()
foo()
sys.stdout = save_stdout

for elegance, a context is best, e.g:

import contextlib
import io
import sys

@contextlib.contextmanager
def nostdout():
    save_stdout = sys.stdout
    sys.stdout = io.BytesIO()
    yield
    sys.stdout = save_stdout

once you have defined this context, for any block in which you don't want a stdout,

with nostdout():
    foo()

More optimization: you just need to replace sys.stdout with an object that has a no-op write method. For example:

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

to be used the same way as the previous implementation of nostdout. I don't think it gets any cleaner or faster than this;-).

才能让你更想念 2024-09-08 19:28:40

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

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 中

Just to add to what others already said, Python 3.4 introduced the contextlib.redirect_stdout context manager. It accepts a file(-like) object to which the output is to be redirected.

Redirecting to /dev/null will suppress the output:

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

If the wrapped code doesn't write to sys.stdout directly, you can use the simpler

In [15]: with contextlib.redirect_stdout(None):
   ....:     f()
   ....:         

In [16]: 

This solution can be adapted to be used as a decorator:

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

Another possible and occasionally useful solution that will work in both Python 2 and 3 is to pass /dev/null as an argument to f and redirect the output using the file argument of the print function:

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

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

In [16]: 

You can even make target completely optional:

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

Note, you'll need to

from __future__ import print_function

in Python 2.

活雷疯 2024-09-08 19:28:40

我很晚才提出这个问题,我认为这是一个更干净的解决方案。

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)

Chiming in very late to this with what I thought was a cleaner solution to this problem.

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

Usage:

with Suppressor():
    DoMyFunction(*args,**kwargs)
鹤仙姿 2024-09-08 19:28:40

redirect_stdout() 自 python 3.4 起已添加到 contextlib

对于 python >= 3.4,这应该可以做到:

import contextlib
import io

with contextlib.redirect_stdout(io.StringIO()):
    foo()

redirect_stdout() has been added to contextlib since python 3.4

For python >= 3.4, this should do it:

import contextlib
import io

with contextlib.redirect_stdout(io.StringIO()):
    foo()
蔚蓝源自深海 2024-09-08 19:28:40

为什么你认为这是低效的?您测试了吗?顺便说一句,它根本不起作用,因为您正在使用 from ... import 语句。
替换 sys.stdout 就可以了,但不要复制,也不要使用临时文件。打开空设备:

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

Why do you think this is inefficient? Did you test it? By the way, it does not work at all because you are using the from ... import statement.
Replacing sys.stdout is fine, but don't make a copy and don't use a temporary file. Open the null device instead:

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
看轻我的陪伴 2024-09-08 19:28:40

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)

A slight modification to Alex Martelli's answer...

This addresses the case where you always want to suppress stdout for a function instead of individual calls to the function.

If foo() was called many times would it might be better/easier to wrap the function (decorate it). This way you change the definition of foo once instead of encasing every use of the function in a with-statement.

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)
我不咬妳我踢妳 2024-09-08 19:28:40

通过进一步概括,您可以获得一个很好的装饰器,它可以捕获输出甚至返回它:

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

By generalizing even more, you can get a nice decorator that can capture the ouput and even return it:

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
九歌凝 2024-09-08 19:28:40

我认为没有比这更干净或更快的了;-)

呸!我想我可以做得更好一点:-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

这达到了我最初想要的,正常抑制输出,但如果抛出错误则显示抑制的输出。

I don't think it gets any cleaner or faster than this;-)

Bah! I think I can do a little better :-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

Which gets to what I wanted originally, to suppress output normally but to show the suppressed output if an error was thrown.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文