2010-05-13 11 views
36

¿Hay alguna manera en Python de silenciar stdout sin completar una llamada a función como la siguiente?Silenciar el stdout de una función en Python sin descartar sys.stdout y restaurar cada llamada de función

original Código roto:

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

Editar: Se ha corregido el código de Alex Martelli

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

De esa manera los trabajos, pero parece ser terriblemente ineficiente. Hay tiene para ser una mejor manera. ¿Algunas ideas?

+1

yo diría que usted debe dejar sin corregir, ya que Alex ya lo hizo por ti. Tendría más sentido para quién está leyendo. – cregox

+0

Cawas: voy a agregar mi primer corrector sin corregir por encima. O algo similar con los mismos errores. Buena llamada –

+0

relacionada: [Redirecciona temporalmente stdout/stderr] (http://stackoverflow.com/q/6796492/4279) – jfs

Respuesta

69

Asignación de la variable stdout como que estás haciendo no tiene ningún efecto, suponiendo foo contiene declaraciones print - otro ejemplo de por qué nunca debe importar cosas de dentro un módulo (como se hace aquí), pero siempre un módulo como un todo (luego use nombres calificados). El copy es irrelevante, por cierto. La correcta equivalente de su 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 para hacerlo más elegante o rápido. Por ejemplo, podría utilizar un objeto de tipo fichero en memoria en lugar del archivo 'basura':

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 desea una salida estándar,

with nostdout(): 
    foo() 

Más optimización: sólo tiene que sustituir sys.stdout con un objeto que tiene un no-op write método. 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 

a usarse del mismo modo que la aplicación previa de nostdout. No creo que sea más limpio o más rápido que esto ;-).

+0

Alex Martelli: ese último parece demasiado bonito. Nunca antes había usado la palabra clave "con". Voy a tener que investigarlo. –

+10

Debe tenerse en cuenta que si utiliza esto en un entorno con hebras, su sustitución se aplicará a * todos * hilos. Por lo tanto, si lo usa en un servidor web con hilos, por ejemplo, stdout quedará destrozado para todos los hilos (durante la duración de esa llamada de función, que será un trozo de tiempo aleatorio en otros hilos). Manejar este caso es difícil, e involucraría 'enhebrar'.local' (en general, recomendaría tratar de evitar los hilos y la redirección de stdout). –

+0

He votado negativamente porque hace que el código se cuelgue, de modo que no se puede interrumpir, por lo que puedo decir, si se produce una excepción dentro del contexto. Creo que [la respuesta de @bitmous] (http://stackoverflow.com/a/40054132/1194883) es más robusta * y * más elegante de una manera muy inteligente (sin necesidad de la clase 'DummyFile'). – Mike

14

¿Por qué crees que esto es ineficiente? ¿Hizo prueba? Por cierto, no funciona en absoluto porque está utilizando la declaración from ... import. Reemplazar sys.stdout está bien, pero no haga una copia y no use un archivo temporal. Abrir 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 
+0

La única razón por la que creo que es ineficiente porque es un archivo de escritura. Las escrituras de archivo son definitivamente más lentas de lo necesario si lo hace lo suficiente. Pero sí arruiné mi código inicial, lo arreglé en base al comentario inicial de Alex –

+4

. Debe ser 'open (os.devnull, 'w')'. En general, 'os.devnull' es útil si necesita un objeto de archivo * real *. Pero 'print' acepta cualquier cosa con un método' write() '. – jfs

+1

+1 para os.devnull .. sorprendió la respuesta ganadora propagó el uso de un archivo real para la salida. – synthesizerpatel

4

Una ligera modificación a Alex Martelli's answer ...

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

Si se llamó a foo() muchas veces, sería mejor/más fácil ajustar la función (decorarla). De esta forma puede cambiar la definición de foo una vez en lugar de incluir cada uso de la función en una declaración con.

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) 
+0

tgray: Me gusta mucho ese enfoque. ¿Es realmente más rápido? Puede ahorrar un poco de tiempo si no lo hago, supongo que dos jmps y menos empujando y tirando de la pila. Eso y puedo llamar por exceso a todas las funciones que llamaré. ¿Es su otra ventaja oculta que no estoy viendo? –

+0

Tim: Su principal ventaja es ahorrar tiempo al programador reduciendo la cantidad de cambios que necesita realizar en el código para desactivar la salida estándar de una función. No he ejecutado una prueba de rendimiento contra el gestor de contexto aún. – tgray

+0

tgray: Eso es lo que yo pensaba. Aunque es una idea genial. Me gusta. Creo que ambos enfoques son buenos dependiendo de lo que estás intentando hacer. IE, si intentas reemplazar una llamada en 100s de spot de esa manera, es mejor, pero si quieres hacerlo para que una función pase a otra función, creo que la otra tiene sus ventajas. Me gusta aunque –

2

Generalizando aún más, se puede obtener un buen decorador que puede capturar que 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 
2

no creo que llegue cualquier limpiador o más rápido que esto; -)

Bah! Creo que puedo hacer un poco mejor :-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 

que se hace a lo que quería en un principio, para suprimir la salida normalmente sino para mostrar la salida suprimida si se produce un error.

10

Para agregar a lo que otros ya han dicho, Python 3.4 introdujo el administrador de contexto contextlib.redirect_stdout. Acepta un objeto de archivo (-like) al que se debe redirigir la salida.

a redirigir a /dev/null se 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 utilizado como un 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 posible y ocasionalmente una solución útil que funcionará en Python 2 y 3 es pasar /dev/null como un argumento a f y redirigir la salida con el argumento de la función fileprint:

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

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

In [16]: 

Puede incluso hacer target completamente opcional:

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

Nota, que tendrá que

from __future__ import print_function 

en Python 2.

+0

La solución más elegante para Python 3.4 y posteriores. – dm295

8

Acompañando 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) 
+0

Esto es bueno porque maneja excepciones dentro del contexto. Y el uso de 'self' como el trivial' write'r es muy inteligente. – Mike

+0

Esta es sin duda la mejor respuesta. Sugerencia para cualquier persona no está seguro: ̶ Si desea que los errores que se arrojen como normal, basta con sustituir ̶ '# Do excepción normales handling' con ̶'raise'. He realizado una edición – Anonymous

+0

También debe mencionarse, aquí, que si tiene algún rastro de depurador configurado en 'DoMyFunction()', serán invisibles, el programa simplemente esperará en el rastreo sin avisar. – Anonymous

Cuestiones relacionadas