Silencie la salida estándar de una función en Python sin tener que descartar sys.stdout y restaurar cada llamada de función

¿Hay alguna manera en Python para silenciar la salida estándar sin ajustar una llamada de función como la siguiente?

Código roto original:

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

Edición: Código corregido de Alex Martelli

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

Esa forma funciona pero parece ser terriblemente ineficiente. Tiene que haber una mejor manera. ¿Algunas ideas?

Asignar la variable stdout como lo está haciendo no tiene ningún efecto, suponiendo que foo contiene sentencias de print , otro ejemplo de por qué nunca debería importar cosas desde dentro de un módulo (como lo está haciendo aquí), pero siempre un módulo como entero (luego usar nombres calificados). La copy es irrelevante, por cierto. El equivalente correcto de tu fragmento es:

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

Ahora , cuando el código es correcto, es el momento de hacerlo más elegante o rápido. Por ejemplo, podría usar un objeto similar a un archivo en la memoria en lugar del archivo ‘papelera’:

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

Para la elegancia, un contexto es mejor, por ejemplo:

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

una vez que haya definido este contexto, para cualquier bloque en el que no desee una salida estándar,

 with nostdout(): foo() 

Más optimización: solo necesita reemplazar sys.stdout con un objeto que no tenga un método de write . Por ejemplo:

 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 

para ser utilizado de la misma manera que la implementación anterior de nostdout . No creo que se ponga más limpio o más rápido que esto ;-).

Solo para agregar a lo que otros ya han dicho, Python 3.4 introdujo el contextlib.redirect_stdout contexto contextlib.redirect_stdout . Acepta un objeto de archivo (como) al que se redirige la salida.

Redirigir a / dev / null suprimirá la salida:

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

Esta solución se puede adaptar para ser utilizada como decorador:

 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 

Otra solución posible y ocasionalmente útil que funcionará tanto en Python 2 como en 3 es pasar / dev / null como un argumento para f y redirigir la salida usando el argumento de file de la función de print :

 In [14]: def f(target): print('noise', file=target) In [15]: with open(os.devnull, 'w') as devnull: ....: f(target=devnull) ....: In [16]: 

Incluso puedes hacer que el target completamente opcional:

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

Nota, necesitarás

 from __future__ import print_function 

en Python 2.

¿Por qué crees que esto es ineficiente? ¿Lo probaste ? Por cierto, no funciona en absoluto porque está utilizando la instrucción from ... import . Reemplazar sys.stdout está bien, pero no haga una copia y no use un archivo temporal. Abra el dispositivo nulo en su lugar:

 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 

Entiendo muy tarde a esto con lo que pensé que era una solución más limpia a este problema.

 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 

Uso:

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

Una ligera modificación a la respuesta de Alex Martelli …

Esto aborda el caso en el que siempre desea suprimir la stdout para una función en lugar de llamadas individuales a la función.

Si se llamara foo() muchas veces, sería mejor / más fácil envolver la función (decorarla). De esta manera, cambia la definición de foo una vez en lugar de incluir cada uso de la función en una sentencia 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) 

Al generalizar aún más, puede obtener un bonito decorador que puede capturar la salida e incluso devolverlo:

 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 

No creo que se ponga más limpio o más rápido que esto 😉

¡Bah! Creo que puedo hacerlo un poco mejor 😀

 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 

Que llega a lo que quería originalmente, para suprimir la salida normalmente, pero para mostrar la salida suprimida si se produce un error.

redirect_stdout () se ha agregado a contextlib desde python 3.4

Para python> = 3.4, esto debería hacerlo:

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