Подтвердить что ты не робот

Подавлять вывод stdout/stderr из функций Python

У меня есть Python script, который использует некоторые функции Python с закрытым ящиком (т.е. я не могу редактировать эти функции), предоставленные моим работодателем. Когда я вызываю эти функции, они выводят вывод на мой терминал linux, который я хотел бы подавить. Я попытался перенаправить stdout/stderr через;

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

но это не позволяет получить выход. Я думаю, что функции, которые я вызываю через Python (rogue_function() сверху), действительно являются обертками для скомпилированного C-кода, который фактически выполняет печать.

Кто-нибудь знает, как я могу сделать "глубокий захват" любой печати, переданной в stdout/stderr с помощью функции (и любых подфункций, которые выполняют вызовы)?

UPDATE

В итоге я взял метод, описанный в выбранном ниже ответе, и написал диспетчер контекстов, чтобы подавить 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()

Это работает "довольно хорошо". Он подавляет распечатку от функций изгоев, которые загромождали мой script. Я заметил в тестировании, что он позволяет через поднятые исключения, а также некоторые логгер печати, и я не совсем понимаю, почему. Я думаю, что это связано с тем, когда эти сообщения отправляются в stdout/stderr (я думаю, что это происходит после выхода из контекстного менеджера). Если кто-нибудь сможет это подтвердить, мне будет интересно узнать подробности...

4b9b3361

Ответ 1

Этот подход (найденный через связанную боковую панель) может работать. Он переназначает дескрипторы файлов, а не только обертки для них в sys.stdout и т.д.

Ответ 2

Вы тоже пытались перенаправить 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).

Ответ 3

Мое решение похоже на ваше, но использует contextlib и немного короче и понятнее (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()

Контекст, почему я создал это, находится в этом сообщении в блоге. Похоже на твое мнение.

Я использую его так: setup.py:

with stdchannel_redirected(sys.stderr, os.devnull):
    if compiler.has_function('clock_gettime', libraries=['rt']):
        libraries.append('rt')

Ответ 4

Начиная с Python 3.5 мы можем сделать это с минимальной работой, используя встроенные в contextlib встроенные модули, а именно redirect_stdout и redirect_stderr. Нам нужно только объединить эти два встроенных менеджера контекста в нашем собственном менеджере контекста, что можно легко сделать, используя красивый шаблон в ответе Мартина здесь. Перенаправление обоих выходных os.devnull на 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'

до терминала. Необработанные исключения никогда не должны оставаться незамеченными.

Ответ 5

Не запрошен 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)

EDIT: теперь позволяет выбирать stderr, stdout или и то, и другое, как в Hider([stdout, stderr])

Ответ 6

Я использую декоратор для этого. Он сохраняет sys.stdout и sys.stderr и делает эти переменные 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

Ответ 7

рабочая версия 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()

Ответ 8

Если вы используете этот script на машине на основе Linux, вы должны иметь возможность:

$> ./runscript.py > output.txt