Временно перенаправить stdout/stderr


можно ли временно перенаправить stdout/stderr в Python (т. е. на время действия метода)?

Edit:

проблема с текущими решениями (которые я сначала вспомнил, но потом забыл) заключается в том, что они не редирект; скорее, они просто заменяют потоки в полном объеме. Следовательно, если метод имеет local скопировать одной переменной по какой-либо причине (например, потому что поток был передан в качестве параметра что-то), это не сработает.

какие решения?

9 53

9 ответов:

чтобы решить проблему, что некоторые функции могли бы кэшировать sys.stdout поток как локальная переменная и, следовательно, замена глобального sys.stdout не будет работать внутри этой функции, вы можете перенаправить на уровне дескриптора файла (sys.stdout.fileno()) например:

from __future__ import print_function
import os
import sys

def some_function_with_cached_sys_stdout(stdout=sys.stdout):
    print('cached stdout', file=stdout)

with stdout_redirected(to=os.devnull), merged_stderr_stdout():
    print('stdout goes to devnull')
    some_function_with_cached_sys_stdout()
    print('stderr also goes to stdout that goes to devnull', file=sys.stderr)
print('stdout is back')
some_function_with_cached_sys_stdout()
print('stderr is back', file=sys.stderr)

stdout_redirected() перенаправляет все выходные данные для sys.stdout.fileno() к заданному имени файла, файловому объекту или файловому дескриптору (os.devnull в Примере).

stdout_redirected() и merged_stderr_stdout() определены здесь.

вы также можете поместить логику перенаправления в contextmanager.

import os
import sys

class RedirectStdStreams(object):
    def __init__(self, stdout=None, stderr=None):
        self._stdout = stdout or sys.stdout
        self._stderr = stderr or sys.stderr

    def __enter__(self):
        self.old_stdout, self.old_stderr = sys.stdout, sys.stderr
        self.old_stdout.flush(); self.old_stderr.flush()
        sys.stdout, sys.stderr = self._stdout, self._stderr

    def __exit__(self, exc_type, exc_value, traceback):
        self._stdout.flush(); self._stderr.flush()
        sys.stdout = self.old_stdout
        sys.stderr = self.old_stderr

if __name__ == '__main__':

    devnull = open(os.devnull, 'w')
    print('Fubar')

    with RedirectStdStreams(stdout=devnull, stderr=devnull):
        print("You'll never see me")

    print("I'm back!")

Я не уверен, что означает временное перенаправление. Но вы можете переназначить потоки, как это и сбросить его обратно.

temp = sys.stdout
sys.stdout = sys.stderr
sys.stderr = temp

и записи в sys.stderr в пределах печати stmts, как это.

 print >> sys.stderr, "Error in atexit._run_exitfuncs:"

обычная печать будет в stdout.

это возможно с декоратором, например:

import sys

def redirect_stderr_stdout(stderr=sys.stderr, stdout=sys.stdout):
    def wrap(f):
        def newf(*args, **kwargs):
            old_stderr, old_stdout = sys.stderr, sys.stdout
            sys.stderr = stderr
            sys.stdout = stdout
            try:
                return f(*args, **kwargs)
            finally:
                sys.stderr, sys.stdout = old_stderr, old_stdout

        return newf
    return wrap

как использовать:

@redirect_stderr_stdout(some_logging_stream, the_console):
def fun(...):
    # whatever

или, если вы не хотите изменить источник fun, назовите его непосредственно как

redirect_stderr_stdout(some_logging_stream, the_console)(fun)

но обратите внимание, что это не является потокобезопасным.

начиная с python 3.4 есть контекстный менеджер contextlib.redirect_stdout:

from contextlib import redirect_stdout

with open('yourfile.txt', 'w') as f:
    with redirect_stdout(f):
        # do stuff...

в полной тишине stdout это работает:

from contextlib import redirect_stdout

with redirect_stdout(None):
    # do stuff...

вот контекстный менеджер, который я нашел полезным. Хорошие вещи об этом, что вы можете использовать его с with оператор и он также обрабатывает перенаправление для дочерних процессов.

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):
        ...
    """

    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()

контекст для того, почему я создал это в этот блог.

Компания Raymond Hettinger показывает нам лучший способ[1]:

import sys
with open(filepath + filename, "w") as f: #replace filepath & filename
    with f as sys.stdout:
        print("print this to file")   #will be written to filename & -path

после блока с блоком sys.стандартный вывод будет сброшен

[1]: http://www.youtube.com/watch?v=OSGv2VnC0go&list=PLQZM27HgcgT-6D0w6arhnGdSHDcSmQ8r3

мы будем использовать PHP синтаксис ob_start и ob_get_contents функции в python3, и перенаправить входные данные в файл.

выходные данные хранятся в файле, также может использоваться любой тип потока.

from functools import partial
output_buffer = None
print_orig = print
def ob_start(fname="print.txt"):
    global print
    global output_buffer
    print = partial(print_orig, file=output_buffer)
    output_buffer = open(fname, 'w')
def ob_end():
    global output_buffer
    close(output_buffer)
    print = print_orig
def ob_get_contents(fname="print.txt"):
    return open(fname, 'r').read()

использование:

print ("Hi John")
ob_start()
print ("Hi John")
ob_end()
print (ob_get_contents().replace("Hi", "Bye"))

печати

Привет, Джон Пока Джон