web-dev-qa-db-fra.com

Supprimer l'impression stdout/stderr des fonctions Python

J'ai un script Python qui utilise certaines fonctions Python «fermées» (c'est-à-dire que je ne peux pas modifier ces fonctions) fournies par mon employeur. Lorsque j'appelle ces fonctions, elles impriment une sortie sur mon terminal Linux que je souhaite supprimer. J'ai essayé de rediriger stdout/stderr via;

orig_out = sys.stdout
sys.stdout = StringIO()
rogue_function()
sys.stdout = orig_out

mais cela ne parvient pas à attraper la sortie. Je pense que les fonctions que j'appelle via-Python (rogue_function () ci-dessus) sont en fait des wrappers pour le code C compilé, qui effectue réellement l'impression. 

Est-ce que quelqu'un connaît un moyen de "capturer en profondeur" toute impression remise à stdout/stderr par une fonction (et toute sous-fonction qui appelle des appels)? 

METTRE À JOUR:

J'ai fini par prendre la méthode décrite dans la réponse sélectionnée ci-dessous et écrire un gestionnaire de contexte pour supprimer stdout et stderr:

# Define a context manager to suppress stdout and stderr.
class suppress_stdout_stderr(object):
    '''
    A context manager for doing a "deep suppression" of stdout and stderr in 
    Python, i.e. will suppress all print, even if the print originates in a 
    compiled C/Fortran sub-function.
       This will not suppress raised exceptions, since exceptions are printed
    to stderr just before a script exits, and after the context manager has
    exited (at least, I think that is why it lets exceptions through).      

    '''
    def __init__(self):
        # Open a pair of null files
        self.null_fds =  [os.open(os.devnull,os.O_RDWR) for x in range(2)]
        # Save the actual stdout (1) and stderr (2) file descriptors.
        self.save_fds = [os.dup(1), os.dup(2)]

    def __enter__(self):
        # Assign the null pointers to stdout and stderr.
        os.dup2(self.null_fds[0],1)
        os.dup2(self.null_fds[1],2)

    def __exit__(self, *_):
        # Re-assign the real stdout/stderr back to (1) and (2)
        os.dup2(self.save_fds[0],1)
        os.dup2(self.save_fds[1],2)
        # Close all file descriptors
        for fd in self.null_fds + self.save_fds:
            os.close(fd)

Pour l'utiliser, il vous suffit de:

with suppress_stdout_stderr():
    rogue_function()

Cela fonctionne "très bien". Cela supprime l'impression des fonctions non autorisées qui encombraient mon script. En le testant, j'ai remarqué qu'il laissait passer des exceptions levées ainsi que des empreintes d'enregistrement, et je ne comprends pas très bien pourquoi. Je pense que cela a quelque chose à voir avec quand ces messages sont envoyés à stdout/stderr (je pense que cela se produit après la sortie de mon gestionnaire de contexte). Si quelqu'un peut le confirmer, j'aimerais connaître les détails ... 

26
jeremiahbuddha

Cette approche (trouvée dans la barre latérale correspondante) pourrait fonctionner. Il réaffecte les descripteurs de fichier plutôt que simplement les wrappers dans sys.stdout, etc.

6
Dougal

Ma solution est similaire à la vôtre, mais utilise contextlib. Elle est un peu plus courte et plus facile à comprendre (IMHO).

import contextlib


@contextlib.contextmanager
def stdchannel_redirected(stdchannel, dest_filename):
    """
    A context manager to temporarily redirect stdout or stderr

    e.g.:


    with stdchannel_redirected(sys.stderr, os.devnull):
        if compiler.has_function('clock_gettime', libraries=['rt']):
            libraries.append('rt')
    """

    try:
        oldstdchannel = os.dup(stdchannel.fileno())
        dest_file = open(dest_filename, 'w')
        os.dup2(dest_file.fileno(), stdchannel.fileno())

        yield
    finally:
        if oldstdchannel is not None:
            os.dup2(oldstdchannel, stdchannel.fileno())
        if dest_file is not None:
            dest_file.close()

Le contexte pour lequel j'ai créé ceci est à cet article de blog . Semblable à la vôtre je pense.

Je l'utilise comme ceci dans un setup.py:

with stdchannel_redirected(sys.stderr, os.devnull):
    if compiler.has_function('clock_gettime', libraries=['rt']):
        libraries.append('rt')
2
Marc Abramowitz

Pas vraiment demandé par l'OP, mais j'avais besoin de cacher et de stocker la sortie, et j'ai fait comme suit:

from io import StringIO
import sys

class Hider:
    def __init__(self, channels=('stdout',)):
        self._stomach = StringIO()
        self._orig = {ch : None for ch in channels}

    def __enter__(self):
        for ch in self._orig:
            self._orig[ch] = getattr(sys, ch)
            setattr(sys, ch, self)
        return self

    def write(self, string):
        self._stomach.write(string)

    def flush(self):
        pass

    def autopsy(self):
        return self._stomach.getvalue()

    def __exit__(self, *args):
        for ch in self._orig:
            setattr(sys, ch, self._orig[ch])

Usage:

with Hider() as h:
    spammy_function()
    result = h.autopsy()

(testé uniquement avec Python 3)

EDIT: permet maintenant de sélectionner stderr, stdout ou les deux, comme dans Hider([stdout, stderr])

1
Pietro Battiston

Avez-vous essayé de rediriger stderr aussi? P. Ex.

sys.stdout = StringIO();
sys.stderr = StringIO();
foo(bar);
sys.stdout = sys.__stdout__; # These are provided by python
sys.stderr = sys.__stderr__;

Également utiliser StringIO pourrait utiliser de la mémoire supplémentaire. Vous pouvez utiliser un périphérique factice à la place (par exemple http://coreygoldberg.blogspot.com/2009/05/python-redirect-or-turn-off-stdout-and.html ).

1
Bob

A partir de python 3.5, nous pouvons le faire avec un travail minimal en utilisant les éléments intégrés dans contextlib , à savoir redirect_stdout et redirect_stderr . Nous avons seulement besoin de combiner ces deux gestionnaires de contexte intégrés dans un de nos gestionnaires de contexte personnalisé, ce qui peut être facilement fait en utilisant le modèle de Nice dans la réponse de Martijn ici . La redirection des deux sorties vers os.devnull doit être suffisamment sûre et portable.

from contextlib import contextmanager,redirect_stderr,redirect_stdout
from os import devnull

@contextmanager
def suppress_stdout_stderr():
    """A context manager that redirects stdout and stderr to devnull"""
    with open(devnull, 'w') as fnull:
        with redirect_stderr(fnull) as err, redirect_stdout(fnull) as out:
            yield (err, out)

Notez que la suppression de stderr vous donnera toujours une trace complète quand quelque chose se brise, ce qui est une bonne chose:

import sys

def rogue_function():
    print('spam to stdout')
    print('important warning', file=sys.stderr)
    1 + 'a'
    return 42

with suppress_stdout_stderr():
    rogue_function()

Lorsque exécuté ci-dessus seulement imprime

Traceback (most recent call last):
  File "tmp.py", line 20, in <module>
    rogue_function()
  File "foo.py", line 16, in rogue_function
    1 + 'a'
TypeError: unsupported operand type(s) for +: 'int' and 'str'

au terminal. Les exceptions non gérées ne doivent jamais passer inaperçues.

1
Andras Deak

version de travail de python 3.6, testé avec des millions de suppressions sans aucune erreur

import os
import sys

class suppress_stdout_stderr(object):
    def __enter__(self):
        self.outnull_file = open(os.devnull, 'w')
        self.errnull_file = open(os.devnull, 'w')

        self.old_stdout_fileno_undup    = sys.stdout.fileno()
        self.old_stderr_fileno_undup    = sys.stderr.fileno()

        self.old_stdout_fileno = os.dup ( sys.stdout.fileno() )
        self.old_stderr_fileno = os.dup ( sys.stderr.fileno() )

        self.old_stdout = sys.stdout
        self.old_stderr = sys.stderr

        os.dup2 ( self.outnull_file.fileno(), self.old_stdout_fileno_undup )
        os.dup2 ( self.errnull_file.fileno(), self.old_stderr_fileno_undup )

        sys.stdout = self.outnull_file        
        sys.stderr = self.errnull_file
        return self

    def __exit__(self, *_):        
        sys.stdout = self.old_stdout
        sys.stderr = self.old_stderr

        os.dup2 ( self.old_stdout_fileno, self.old_stdout_fileno_undup )
        os.dup2 ( self.old_stderr_fileno, self.old_stderr_fileno_undup )

        os.close ( self.old_stdout_fileno )
        os.close ( self.old_stderr_fileno )

        self.outnull_file.close()
        self.errnull_file.close()
0
iperov