web-dev-qa-db-fra.com

Faire taire la sortie standard d'une fonction dans Python sans supprimer sys.stdout et restaurer chaque appel de fonction

Existe-t-il un moyen dans Python pour arrêter la sortie standard sans encapsuler un appel de fonction comme suit?

Code cassé d'origine:

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

Edit: Code corrigé d'Alex Martelli

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

Cela fonctionne mais semble terriblement inefficace. Il a pour être un meilleur moyen. Des idées?

50
Tim McJilton

L'affectation de la variable stdout pendant que vous effectuez n'a aucun effet, en supposant que foo contient des instructions print - encore un autre exemple de la raison pour laquelle vous ne devriez jamais importer des éléments de à l'intérieur d'un module (comme vous le faites ici), mais toujours un module dans son ensemble (alors utilisez des noms qualifiés). Soit dit en passant, le copy n'est pas pertinent. 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, il est temps de le rendre plus élégant ou plus rapide. Par exemple, vous pouvez utiliser un objet de type fichier en mémoire au lieu du fichier "corbeille":

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

pour l'élégance, un contexte est préférable, 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 tout bloc dans lequel vous ne voulez pas de sortie standard,

with nostdout():
    foo()

Plus d'optimisation: il vous suffit de remplacer sys.stdout par un objet qui a une méthode no-op write. 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 de la même manière que l'implémentation précédente de nostdout. Je ne pense pas que cela devienne plus propre ou plus rapide que cela ;-).

80
Alex Martelli

Juste pour ajouter à ce que d'autres ont déjà dit, Python 3.4 a introduit le contextlib.redirect_stdout gestionnaire de contexte. Il accepte un objet fichier (-like) vers lequel la sortie doit être redirigée.

La redirection 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ée pour être utilisée 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


Une autre solution possible et parfois utile qui fonctionnera dans les deux Python 2 et 3 est de passer /dev/null comme un argument vers f et redirigez 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 rendre target complètement facultatif:

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

Notez que vous devrez

from __future__ import print_function

dans Python 2.

19
vaultah

Pourquoi pensez-vous que c'est inefficace? L'avez-vous test cela? Soit dit en passant, cela ne fonctionne pas du tout car vous utilisez le from ... import déclaration. Remplacement de sys.stdout c'est bien, mais ne faites pas de copie et n'utilisez pas de fichier temporaire. Ouvrez le périphérique nul à 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
15
Philipp

Sonner très tard à cela avec ce que je pensais être 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

Usage:

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

Une légère modification de réponse d'Alex Martelli ...

Cela résout le cas où vous souhaitez toujours supprimer stdout pour une fonction au lieu d'appels individuels à la fonction.

Si foo() était appelé plusieurs fois, il serait préférable/plus facile d'envelopper la fonction (de la décorer). De cette façon, vous modifiez 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)
5
tgray

En généralisant encore plus, vous pouvez obtenir un joli décorateur qui peut capturer la sortie et même la 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
e-satis

Je ne pense pas que ça devienne plus propre ou plus rapide que ça; -)

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 revient à ce que je voulais à l'origine, supprimer la sortie normalement mais afficher la sortie supprimée si une erreur était levée.

2
PatB

redirect_stdout () a été ajouté à contextlib depuis python 3.4

Pour python> = 3.4, cela devrait le faire:

import contextlib
import io

with contextlib.redirect_stdout(io.StringIO()):
    foo()
1
Pin-Yen