propias - tipos de funciones en python
Suprimir la impresiĆ³n stdout/stderr de las funciones de Python (7)
Tengo un script de Python que utiliza algunas funciones de Python de caja cerrada (es decir, no puedo editar estas funciones) proporcionadas por mi empleador. Cuando llamo a estas funciones, están imprimiendo la salida a mi terminal de Linux que me gustaría suprimir. He intentado redireccionar stdout / stderr vía;
orig_out = sys.stdout
sys.stdout = StringIO()
rogue_function()
sys.stdout = orig_out
pero esto no logra captar la salida. Creo que las funciones a las que llamo via-Python (rogue_function () desde arriba) son realmente envoltorios para el código C compilado, que en realidad están imprimiendo.
¿Alguien sabe de alguna manera que pueda hacer una "captura profunda" de cualquier impresión que se le dé a stdout / stderr mediante una función (y cualquier subfunción que la función llame)?
ACTUALIZACIÓN :
Terminé tomando el método descrito en la respuesta seleccionada a continuación y escribiendo un administrador de contexto para suprimir stdout y 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)
Para usar esto solo tienes que:
with suppress_stdout_stderr():
rogue_function()
Esto funciona "bastante bien". No suprime la impresión de las funciones deshonestas que saturaban mi script. Noté que, al probarlo, permite detectar excepciones así como algunas impresiones de registradores, y no estoy del todo claro por qué. Creo que tiene algo que ver con el momento en que estos mensajes se envían a stdout / stderr (creo que ocurre después de que se cierre el administrador de contexto). Si alguien puede confirmar esto, me interesaría escuchar los detalles ...
¿Intentaste redirigir stderr también? p.ej
sys.stdout = StringIO();
sys.stderr = StringIO();
foo(bar);
sys.stdout = sys.__stdout__; # These are provided by python
sys.stderr = sys.__stderr__;
También usar StringIO podría usar memoria extra. Puede utilizar un dispositivo ficticio en su lugar (por ejemplo, http://coreygoldberg.blogspot.com/2009/05/python-redirect-or-turn-off-stdout-and.html ).
A partir de Python 3.5 podemos hacer esto con un trabajo mínimo utilizando incorporaciones en contextlib
, a saber, redirect_stdout
y redirect_stderr
. Solo necesitamos combinar estos dos gestores de contexto incorporados en un gestor de contexto personalizado, lo que se puede hacer fácilmente usando el bonito patrón en la respuesta de Martijn aquí . La redirección de ambas salidas a os.devnull
debe ser lo suficientemente segura y portátil.
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)
Tenga en cuenta que la supresión de stderr
todavía le dará un seguimiento completo cuando algo se rompa, lo cual es bueno:
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()
Cuando se ejecute lo anterior solo se imprimen.
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''
a la terminal. Las excepciones no manejadas nunca deben pasar desapercibidas.
En realidad no fue solicitado por el OP, pero necesitaba ocultar y almacenar la salida, e hice lo siguiente:
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])
Uso:
with Hider() as h:
spammy_function()
result = h.autopsy()
(probado solo con Python 3)
EDITAR: ahora permite seleccionar stderr
, stdout
o ambos, como en Hider([stdout, stderr])
Mi solución es similar a la tuya, pero usa contextlib
y es un poco más corta y fácil de entender (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()
El contexto por el que creé esto está en esta publicación de blog . Al igual que el tuyo creo.
Lo uso así en un setup.py
:
with stdchannel_redirected(sys.stderr, os.devnull):
if compiler.has_function(''clock_gettime'', libraries=[''rt'']):
libraries.append(''rt'')
Python 3.6 versión de trabajo, probada con millones de supresiones sin ningún error
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()
Si está ejecutando este script en una máquina basada en Linux, debería poder:
$> ./runscript.py > output.txt
Este enfoque (que se encuentra a través de la barra lateral relacionada) podría funcionar. Reasigna los descriptores de archivo en lugar de solo los envoltorios a ellos en sys.stdout, etc.