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 ...
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.
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')
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])
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 ).
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.
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()