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 ...
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.
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).
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')
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])
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()
à 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.
si vous exécutez ce script sur une machine linux, vous devriez pouvoir:
$> ./runscript.py > output.txt