2010-05-13 15 views
36

Est-il possible de désactiver la sortie stdout en Python sans encapsuler un appel de fonction comme suit?Mettre hors service la sortie standard d'une fonction en Python sans supprimer sys.stdout et restaurer chaque appel de fonction

Code original brisé:

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

Edit: code corrigé de Alex Martelli

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

qui fonctionne de façon, mais semble être terriblement inefficace. Il a pour être un meilleur moyen. Des idées?

+1

Je dirais que vous devriez laisser sans correction, puisque Alex déjà fait pour vous. Cela aurait plus de sens pour qui lit. – cregox

+0

Cawas: Je vais ajouter mon une première non corrigée au-dessus de lui. Ou quelque chose de similaire avec les mêmes erreurs. Bon appel –

+0

lié: [Rediriger temporairement stdout/stderr] (http://stackoverflow.com/q/6796492/4279) – jfs

Répondre

69

Attribution de la variable stdout que vous faites n'a aucun effet, dans l'hypothèse foo contient des déclarations print - encore un autre exemple des raisons pour lesquelles vous ne devriez jamais importer des choses de l'intérieur un module (comme vous faites ici), mais toujours un module dans son ensemble (utilisez alors des noms qualifiés). Le copy est hors de propos, d'ailleurs. L'équivalent correct de votre extrait est:

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

Maintenant, lorsque le code est correct, est le temps de le rendre plus élégant ou rapide. Par exemple, vous pouvez utiliser un lieu de fichier « » trash en mémoire objet de type fichier:

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

pour l'élégance, un contexte est le meilleur, par exemple:

import contextlib 
import io 
import sys 

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

une fois que vous avez défini ce contexte, pour un bloc dans lequel vous ne voulez pas stdout,

with nostdout(): 
    foo() 

optimisation plus: il vous suffit de remplacer sys.stdout avec un objet qui a une no-op write méthode. Par exemple:

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 

à utiliser la même manière que la mise en œuvre précédente de nostdout. Je ne pense pas que ça devienne plus propre ou plus rapide que ça ;-).

+0

Alex Martelli: ce dernier semble trop joli. Je n'ai jamais utilisé le mot-clé "avec" avant. Je vais devoir regarder dedans. –

+10

Il est à noter que si vous l'utilisez dans un environnement à thread, votre substitution s'appliquera à * tous * les threads. Donc, si vous l'utilisez sur un serveur web par exemple, stdout sera saccagé pour tous les threads (pour la durée de cet appel de fonction, qui sera un peu aléatoire dans les autres threads). La manipulation de ce cas est difficile et impliquerait un enfilage.local' (la plupart du temps je recommande d'essayer d'éviter les threads et la redirection stdout). –

+0

Je l'ai downvoted parce qu'il fait bloquer le code - de telle sorte qu'il ne peut pas être interrompu, pour autant que je sache, si une exception est soulevée dans le contexte. Je pense que [@ bitmous's answer] (http://stackoverflow.com/a/40054132/1194883) est plus robuste * et * plus élégant d'une manière très intelligente (pas besoin de la classe 'DummyFile'). – Mike

14

Pourquoi pensez-vous que c'est inefficace? Avez-vous tester il? Soit dit en passant, cela ne fonctionne pas du tout parce que vous utilisez l'instruction from ... import. Remplacement sys.stdout est très bien, mais ne faites pas de copie et n'utilisez pas un fichier temporaire. Ouvrez le périphérique null à la place:

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 seule raison pour laquelle je pense que c'est inefficace parce que c'est un fichier d'écriture. Les écritures de fichier sont certainement plus lentes que nécessaire si vous le faites assez souvent. Mais ouais j'ai gâché mon code initial, je l'ai corrigé en fonction du commentaire initial d'Alex –

+4

Ça devrait être 'open (os.devnull, 'w')'. En général, 'os.devnull' est utile si vous avez besoin d'un objet * real *. Mais 'print' accepte tout avec une méthode' write() '. – jfs

+1

+1 pour os.devnull .. surpris la réponse gagnante propagé l'utilisation d'un fichier réel pour la sortie. – synthesizerpatel

4

Une légère modification Alex Martelli's answer ...

Cela répond au cas où vous voulez toujours supprimer stdout pour une fonction au lieu des appels individuels à la fonction.

Si foo() a été appelée plusieurs fois, il pourrait être préférable/plus facile d'envelopper la fonction (la décorer). De cette façon, vous changez la définition de foo une fois au lieu d'encapsuler chaque utilisation de la fonction dans une instruction 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) 
+0

tgray: J'aime vraiment cette approche. Est-ce vraiment plus rapide? Il peut gagner un peu de temps en ne faisant pas je suppose que deux jmps et moins pousser et tirer de la pile. Cela et je peux appeler toutes les fonctions que je vais appeler. Est-ce leur autre avantage caché que je ne vois pas? –

+0

Tim: Son principal avantage est de gagner du temps en programmeur en réduisant le nombre de changements que vous devez apporter à votre code pour désactiver la sortie standard pour une fonction. Je n'ai pas encore effectué de test de performance sur le gestionnaire de contexte. – tgray

+0

tgray: C'est ce que je pensais. C'est une bonne idée cependant. J'aime ça. Je pense que les deux approches sont bonnes en fonction de ce que vous essayez de faire. IE si vous essayez de remplacer un appel dans 100s de place de cette façon est préférable, mais si vous voulez le faire pour une fonction étant passée dans une autre fonction, je pense que l'autre a ses avantages. Je l'aime bien –

2

En généralisant encore plus, vous pouvez obtenir un beau décorateur qui peut capturer l'ouput et même retourner:

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

Je ne pense pas qu'il ne fasse pas plus propre ou plus que cela; -)

Bah! Je pense que je peux faire un peu mieux :-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 

Ce qui arrive à ce que je voulais à l'origine, pour supprimer la sortie normalement, mais pour montrer la sortie supprimée si une erreur a été levée.

10

Juste pour ajouter à ce que d'autres déjà dit, Python 3.4 a introduit le gestionnaire de contexte contextlib.redirect_stdout. Il accepte un objet de fichier (-comme) auquel la sortie doit être réorienté.

redirigeant vers /dev/null supprimera la sortie:

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

Cette solution peut être adapté pour être utilisé comme décorateur:

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 


Un autre possible et parfois utile solution qui fonctionne à la fois en Python 2 et 3 est de passer /dev/null comme argument pour f et rediriger la sortie en utilisant l'argument file de la fonction print:

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

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

In [16]: 

Vous pouvez même faire target complètement en option:

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

Remarque, vous devrez

from __future__ import print_function 

en Python 2.

+0

Solution la plus élégante pour Python 3.4 et plus récent. – dm295

8

Chiming très tard à cela avec ce que je pensais était une solution plus propre à ce problème.

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 

Utilisation:

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

C'est sympa car il gère les exceptions dans le contexte. Et l'utilisation de 'self' comme le' write'r trivial est très intelligent. – Mike

+0

C'est certainement la meilleure réponse. Hint de Incertain IMPORTE QUI: ̶ Si vous voulez défauts à Jeté normalement, ̶ '# remplaçons jUStE exception normale handling' Do ̶'raise' wıth. J'ai fait un edit – Anonymous

+0

Il devrait aussi être mentionné, ici, que si vous avez des traces de débogueur définies dans 'DoMyFunction()', elles seront invisibles, le programme attendra simplement sur la trace sans invite. – Anonymous