Молчание stdout функции в Python без перебора sys.stdout и восстановления вызова каждой функции
Есть ли способ в 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
Этот способ работает, но кажется ужасно неэффективным. Там должен быть лучший путь. Любые идеи?
Ответы
Ответ 1
Назначение переменной 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 на объект, у которого есть метод 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
. Я не думаю, что он становится чище или быстрее, чем это; -).
Ответ 2
Почему вы думаете, что это неэффективно? Вы проверили это? Кстати, он вообще не работает, потому что вы используете инструкцию 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
Ответ 3
Просто чтобы добавить к тому, что уже сказали другие, Python 3.4 представил contextlib.redirect_stdout
контекста contextlib.redirect_stdout
. Он принимает объект файла (-like), на который должен быть перенаправлен вывод.
Перенаправление на /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.
Ответ 4
В последнее время это было связано с тем, что я считал более чистым решением этой проблемы.
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...
Это относится к случаю, когда вы всегда хотите подавить stdout
для функции вместо отдельных вызовов функции.
Если foo()
было вызвано много раз, было бы лучше/проще обернуть функцию (украсить ее). Таким образом вы меняете определение foo
один раз вместо того, чтобы помещать каждое использование функции в оператор с выражением.
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)
Ответ 6
Обобщая еще больше, вы можете получить красивый декоратор, который может захватить вывод и даже вернуть его:
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
Ответ 7
Я не думаю, что он становится чище или быстрее этого, -)
Ба! Я думаю, я могу сделать немного лучше:-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
Который добирается до того, что я хотел изначально, чтобы нормально подавлять вывод, но показывать подавленный вывод, если была вызвана ошибка.
Ответ 8
redirect_stdout() был добавлен в contextlib начиная с python 3.4
Для python> = 3.4 это должно быть сделано:
import contextlib
import io
with contextlib.redirect_stdout(io.StringIO()):
foo()