Подавить печать stdout / stderr из функций Python
У меня есть скрипт Python, который использует некоторые закрытые функции Python (т.е. я не могу редактировать эти функции), предоставленные моим работодателем. Когда я вызываю эти функции, они выводят вывод на мой терминал Linux, который я хотел бы отключить. Я попытался перенаправить stdout / stderr через;
orig_out = sys.stdout
sys.stdout = StringIO()
rogue_function()
sys.stdout = orig_out
но это не может поймать вывод. Я думаю, что функции, которые я вызываю via-Python (rogue_function() сверху), действительно являются обертками для скомпилированного C-кода, который фактически выполняет печать.
Кто-нибудь знает, как я могу сделать "глубокий захват" любой печати, передаваемой stdout / stderr функцией (и любыми подфункциями, которые вызывает функция)?
ОБНОВЛЕНИЕ:
В итоге я взял метод, описанный в выбранном ответе ниже, и написал менеджер контекста для подавления stdout и 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)
Чтобы использовать это, вы просто:
with suppress_stdout_stderr():
rogue_function()
Это работает "довольно хорошо". Это подавляет распечатку из мошеннических функций, которые загромождают мой сценарий. При тестировании я заметил, что он пропускает повышенные исключения, а также печатает некоторые записи в журнале, и я не совсем понимаю, почему. Я думаю, что это как-то связано с отправкой этих сообщений в stdout / stderr (думаю, это происходит после выхода из моего менеджера контекста). Если кто-нибудь может подтвердить это, мне было бы интересно узнать подробности...
7 ответов
Этот подход (найденный через соответствующую боковую панель) может работать. Он переназначает файловые дескрипторы, а не просто оболочки для них в sys.stdout и т. Д.
Начиная с Python 3.5 мы можем сделать это с минимальными затратами, используя встроенные contextlib
а именно redirect_stdout
а также redirect_stderr
, Нам нужно только объединить эти два встроенных менеджера контекста в нашем собственном менеджере контекста, что может быть легко сделано с использованием хорошего шаблона в ответе Мартина здесь. Перенаправление обоих выходов на os.devnull
должен быть безопасным и достаточно портативным.
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)
Обратите внимание, что подавление stderr
все равно даст вам полную обратную связь, когда что-то сломается, и это хорошо:
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()
При запуске выше только отпечатки
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'
до терминала. Необработанные исключения никогда не должны оставаться незамеченными.
Рабочая версия python 3.6, протестирована с миллионами подавлений без ошибок
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()
Вы тоже пытались перенаправить stderr? например
sys.stdout = StringIO();
sys.stderr = StringIO();
foo(bar);
sys.stdout = sys.__stdout__; # These are provided by python
sys.stderr = sys.__stderr__;
Также использование StringIO может использовать дополнительную память. Вместо этого вы можете использовать фиктивное устройство (например, http://coreygoldberg.blogspot.com/2009/05/python-redirect-or-turn-off-stdout-and.html).
На самом деле не запрашивается OP, но мне нужно было скрыть и сохранить выходные данные, и я сделал следующее:
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])
Использование:
with Hider() as h:
spammy_function()
result = h.autopsy()
(протестировано только с Python 3)
РЕДАКТИРОВАТЬ: теперь позволяет выбрать stderr
, stdout
или оба, как в Hider([stdout, stderr])
Мое решение похоже на ваше, но использует contextlib
и немного короче и проще для понимания (ИМХО).
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()
Контекст, почему я создал это в этом сообщении в блоге. Похоже на твой я думаю.
Я использую это так в setup.py
:
with stdchannel_redirected(sys.stderr, os.devnull):
if compiler.has_function('clock_gettime', libraries=['rt']):
libraries.append('rt')
Я использую декоратор для этого. Экономит sys.stdout
а также sys.stderr
ссылки и заставляет эти переменные указывать на ноль. Затем после выполнения функции извлекаются исходные ссылки. Важно отметить блок try/ исключением, который позволяет извлекать исходные ссылки даже тогда, когда в функции вызывается исключение.
def suppress_std(func):
def wrapper(*args, **kwargs):
stderr_tmp = sys.stderr
stdout_tmp = sys.stdout
null = open(os.devnull, 'w')
sys.stdout = null
sys.stderr = null
try:
result = func(*args, **kwargs)
sys.stderr = stderr_tmp
sys.stdout = stdout_tmp
return result
except:
sys.stderr = stderr_tmp
sys.stdout = stdout_tmp
raise
return wrapper
Использовать:
@suppress_std
def function_std_suppressed():
# code here
Просто используйте Linux/Unix:
./myscript.py 2>/dev/null # gets rid of stderr
./myscript.py 2>/somewhere/myerror.log
Если вы запускаете этот скрипт на компьютере с Linux, вы должны быть в состоянии:
$> ./runscript.py > output.txt