Supprimer l'impression stdout / stderr des fonctions Python

j'ai un script Python qui utilise certaines fonctions Python (i.e. Je ne peux pas éditer ces fonctions) fournies par mon employeur. Quand j'appelle ces fonctions, elles impriment des sorties sur mon terminal linux que j'aimerais supprimer. J'ai essayé de rediriger stdout / stderr:

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

mais cela ne capte pas la sortie. Je pense que les fonctions que j'appelle via-Python (rogue_function () d'en haut) sont vraiment des wrappers pour le C-code compilé, qui font en fait le impression.

est-ce que quelqu'un sait comment je peux faire une "capture en profondeur" de n'importe quelle impression donnée à stdout / stderr par une fonction (et n'importe quelles sous-fonctions que la fonction appelle)?

UPDATE:

j'ai fini par prendre la méthode décrite dans la réponse choisie 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 "assez bien". Il ne réprimer l'impression des fonctions qui encombraient mon script. J'ai remarqué en le testant qu'il permet à travers les exceptions soulevées ainsi que certains Logger print, et je ne suis pas entièrement clair pourquoi. Je pense qu'il a quelque chose à faire avec quand ces messages sont envoyés à stdout / stderr (je pense que ça arrive après la sortie de mon gestionnaire de contexte). Si quelqu'un peut le confirmer, j'aimerais entendre les détails ...

23
demandé sur randlet 2012-06-21 04:46:40

7 réponses

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

5
répondu Dougal 2017-05-23 11:33:13

Avez-vous essayé de rediriger stderr trop? par exemple,

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

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

1
répondu Bob 2012-06-21 03:02:51

ma solution est similaire à la vôtre mais utilise

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 de la raison pour laquelle j'ai créé ceci est à ce billet de blog. Semblable à la vôtre, je pense.

je l'utilise comme ceci:setup.py:

with stdchannel_redirected(sys.stderr, os.devnull):
    if compiler.has_function('clock_gettime', libraries=['rt']):
        libraries.append('rt')
1
répondu Marc Abramowitz 2013-07-19 19:01:46

Pas vraiment demandé par l'OP, mais j'avais besoin de cacher et magasin la sortie, et a 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])

Utilisation:

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

(testé seulement avec Python 3)

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

1
répondu Pietro Battiston 2016-09-20 12:27:41

Python 3.6 version de travail, Testé avec million 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
répondu iperov 2018-05-20 18:32:24

à partir de python 3.5, nous pouvons le faire avec un travail minimal en utilisant des "built-ins" dans contextlib,redirect_stdout et redirect_stderr. Nous avons seulement besoin de combiner ces deux gestionnaires de contexte intégrés dans un gestionnaire de contexte personnalisé de la nôtre, ce qui peut être facilement fait en utilisant le joli motif de Martijn de réponse ici. Rediriger les sorties os.devnull doit être Sécuritaire et portable assez.

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 supprimer stderr va encore vous donner le plein retraçage 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 vous exécutez le ci-dessus ne s'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'

pour le terminal. Les exceptions ne devraient jamais passer inaperçues.

0
répondu Andras Deak 2018-09-21 11:02:34

si vous exécutez ce script sur une machine linux, vous devriez pouvoir:

$> ./runscript.py > output.txt
-2
répondu GeneralBecos 2012-06-21 02:45:25