Отключите стандартный вывод функции в Python, не удаляя sys.stdout и не восстанавливая каждый вызов функции - PullRequest
41 голосов
/ 13 мая 2010

Есть ли в Python способ отключить стандартный вывод без переноса вызова функции следующим образом?

Оригинальный сломанный код:

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

Редактировать: Исправленный код от Алекса Мартелли

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

Этот способ работает, но кажется ужасно неэффективным. Там имеет , чтобы быть лучше. Есть идеи?

Ответы [ 8 ]

78 голосов
/ 13 мая 2010

Назначение переменной 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. Я не думаю, что это становится чище или быстрее, чем это; -).

18 голосов
/ 04 февраля 2015

Просто чтобы добавить к тому, что уже говорили другие, 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 голосов
/ 13 мая 2010

Почему вы считаете это неэффективным? Вы тестировали это? Кстати, он вообще не работает, потому что вы используете оператор 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
13 голосов
/ 15 октября 2016

Звонок очень поздно к этому, что, как я думал, было более чистым решением этой проблемы.

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 голосов
/ 13 мая 2010

Небольшое изменение Ответ Алекса Мартелли ...

Это относится к случаю, когда вы всегда хотите подавить 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 голосов
/ 19 июня 2014

Я не думаю, что это становится чище или быстрее, чем это; -)

Ба! Я думаю, что я могу сделать немного лучше: -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

То, что я получил изначально, для обычного подавления вывода, но для вывода подавленного вывода в случае возникновения ошибки.

2 голосов
/ 14 мая 2012

Обобщая еще больше, вы можете получить хороший декоратор, который может захватывать выходные данные и даже возвращать их:

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
1 голос
/ 17 июля 2018

redirect_stdout () был добавлен в contextlib начиная с python 3.4

Для python> = 3.4 это должно быть сделано:

import contextlib
import io

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