Перенаправить stdout в файл в Python?
Как перенаправить stdout на произвольный файл в Python?
Когда запущенный Python script (например, веб-приложение) запускается из сеанса ssh и завершается, а сеанс ssh закрывается, приложение будет увеличивать IOError и не сможет выполнить попытку записи в stdout, Мне нужно было найти способ сделать приложение и модули выводятся в файл, а не stdout, чтобы предотвратить сбой из-за IOError. В настоящее время я использую nohup для перенаправления вывода в файл, и это выполняет эту работу, но мне было интересно, есть ли способ сделать это, не используя nohup, из любопытства.
Я уже пробовал sys.stdout = open('somefile', 'w')
, но это не мешает некоторым внешним модулям выводить на терминал (или, может быть, строка sys.stdout = ...
вообще не срабатывает). Я знаю, что он должен работать с более простыми сценариями, которые я тестировал, но у меня еще не было времени на тестирование в веб-приложении.
Ответы
Ответ 1
Если вы хотите выполнить перенаправление внутри скрипта Python, установите sys.stdout
в файловый объект.
import sys
sys.stdout = open('file', 'w')
print('test')
Гораздо более распространенным методом является использование перенаправления оболочки при выполнении (то же самое в Windows и Linux):
$ python foo.py > file
Ответ 2
В Python 3.4 есть contextlib.redirect_stdout()
function:
from contextlib import redirect_stdout
with open('help.txt', 'w') as f:
with redirect_stdout(f):
print('it now prints to `help.text`')
Он похож на:
import sys
from contextlib import contextmanager
@contextmanager
def redirect_stdout(new_target):
old_target, sys.stdout = sys.stdout, new_target # replace sys.stdout
try:
yield new_target # run some code with the replaced stdout
finally:
sys.stdout = old_target # restore to the previous value
который можно использовать в более ранних версиях Python. Последняя версия не reusable. Его можно сделать одним, если это необходимо.
Он не перенаправляет stdout на уровне дескрипторов файлов, например:
import os
from contextlib import redirect_stdout
stdout_fd = sys.stdout.fileno()
with open('output.txt', 'w') as f, redirect_stdout(f):
print('redirected to a file')
os.write(stdout_fd, b'not redirected')
os.system('echo this also is not redirected')
b'not redirected'
и 'echo this also is not redirected'
не перенаправляются в файл output.txt
.
Для перенаправления на уровне дескриптора файла можно использовать os.dup2()
:
import os
import sys
from contextlib import contextmanager
def fileno(file_or_fd):
fd = getattr(file_or_fd, 'fileno', lambda: file_or_fd)()
if not isinstance(fd, int):
raise ValueError("Expected a file (`.fileno()`) or a file descriptor")
return fd
@contextmanager
def stdout_redirected(to=os.devnull, stdout=None):
if stdout is None:
stdout = sys.stdout
stdout_fd = fileno(stdout)
# copy stdout_fd before it is overwritten
#NOTE: `copied` is inheritable on Windows when duplicating a standard stream
with os.fdopen(os.dup(stdout_fd), 'wb') as copied:
stdout.flush() # flush library buffers that dup2 knows nothing about
try:
os.dup2(fileno(to), stdout_fd) # $ exec >&to
except ValueError: # filename
with open(to, 'wb') as to_file:
os.dup2(to_file.fileno(), stdout_fd) # $ exec > to
try:
yield stdout # allow code to be run with the redirected stdout
finally:
# restore stdout to its previous value
#NOTE: dup2 makes stdout_fd inheritable unconditionally
stdout.flush()
os.dup2(copied.fileno(), stdout_fd) # $ exec >&copied
Тот же пример работает теперь, если вместо redirect_stdout()
используется stdout_redirected()
:
import os
import sys
stdout_fd = sys.stdout.fileno()
with open('output.txt', 'w') as f, stdout_redirected(f):
print('redirected to a file')
os.write(stdout_fd, b'it is redirected now\n')
os.system('echo this is also redirected')
print('this is goes back to stdout')
Результат, который ранее был напечатан на stdout, теперь находится в output.txt
, пока активен менеджер контекста stdout_redirected()
.
Примечание: stdout.flush()
не сбрасывается
C stdio на Python 3, где ввод-вывод реализуется непосредственно на системных вызовах read()
/write()
. Чтобы очистить все открытые выходные потоки C stdio, вы можете явно вызывать libc.fflush(None)
, если какое-то расширение C использует ввод/вывод на основе stdio:
try:
import ctypes
from ctypes.util import find_library
except ImportError:
libc = None
else:
try:
libc = ctypes.cdll.msvcrt # Windows
except OSError:
libc = ctypes.cdll.LoadLibrary(find_library('c'))
def flush(stream):
try:
libc.fflush(None)
stream.flush()
except (AttributeError, ValueError, IOError):
pass # unsupported
Вы можете использовать параметр stdout
для перенаправления других потоков, а не только sys.stdout
, например, для объединения sys.stderr
и sys.stdout
:
def merged_stderr_stdout(): # $ exec 2>&1
return stdout_redirected(to=sys.stdout, stdout=sys.stderr)
Пример:
from __future__ import print_function
import sys
with merged_stderr_stdout():
print('this is printed on stdout')
print('this is also printed on stdout', file=sys.stderr)
Примечание: stdout_redirected()
смешивает буферизованный ввод-вывод (обычно sys.stdout
) и небуферизованный ввод-вывод (операции с файловыми дескрипторами напрямую). Остерегайтесь буферизации .
Чтобы ответить, ваше редактирование: вы можете использовать python-daemon
, чтобы демонтировать ваш script и использовать модуль logging
(в качестве @erikb85 предложил) вместо операторов print
и просто перенаправлял stdout для вашего долгого Python script, который вы используете с помощью nohup
.
Ответ 3
вы можете попробовать это намного лучше
import sys
class Logger(object):
def __init__(self, filename="Default.log"):
self.terminal = sys.stdout
self.log = open(filename, "a")
def write(self, message):
self.terminal.write(message)
self.log.write(message)
sys.stdout = Logger("yourlogfilename.txt")
print "Hello world !" # this is should be saved in yourlogfilename.txt
Ответ 4
Другие ответы не охватывают случай, когда вы хотите, чтобы разветвленные процессы делили ваш новый stdout.
Для этого:
from os import open, close, dup, O_WRONLY
old = dup(1)
close(1)
open("file", O_WRONLY) # should open on 1
..... do stuff and then restore
close(1)
dup(old) # should dup to 1
close(old) # get rid of left overs
Ответ 5
Цитируется из PEP 343. - Оператор "с" (добавлен оператор импорта):
Временно перенаправить стандартный вывод:
import sys
from contextlib import contextmanager
@contextmanager
def stdout_redirected(new_stdout):
save_stdout = sys.stdout
sys.stdout = new_stdout
try:
yield None
finally:
sys.stdout = save_stdout
Используется следующим образом:
with open(filename, "w") as f:
with stdout_redirected(f):
print "Hello world"
Конечно, это не потокобезопасно, но ни один из них не выполняет этот же танец вручную. В однопоточных программах (например, в скриптах) это популярный способ ведения дел.
Ответ 6
import sys
sys.stdout = open('stdout.txt', 'w')
Ответ 7
Вам нужен терминальный мультиплексор, например tmux или Экран GNU
Я удивлен, что небольшой комментарий Райана Амоса к первоначальному вопросу - это единственное упоминание о решении, которое гораздо предпочтительнее для всех остальных, независимо от того, насколько умны хитрость python и сколько их стоит, получив. В дополнение к комментарию Ryan, tmux - хорошая альтернатива экрану GNU.
Но принцип тот же: если вы когда-нибудь захотите оставить работу на терминале, когда вы выходите из системы, идите в кафе для бутерброда, поп в ванную, идите домой (и т.д.), а затем, повторно подключитесь к терминальному сеансу из любого места или любого компьютера, как будто вы никогда не были в отъезде, терминальные мультиплексоры являются ответом. Подумайте о них как VNC или удаленный рабочий стол для сеансов терминала. Все остальное - обходной путь. В качестве бонуса, когда приходит босс и/или партнер, и вы случайно создаете ctrl-w/cmd-w в своем оконном окне вместо окна вашего браузера с его изворотливым контентом, вы не потеряете последние 18 часов обработки
Ответ 8
На основе этого ответа: fooobar.com/questions/7962/..., вот еще один способ, который я понял, который я использую в одном из моих проектов. Что бы вы ни заменили sys.stderr
или sys.stdout
, вам нужно убедиться, что замена соответствует интерфейсу file
, особенно если это то, что вы делаете, потому что stderr/stdout используются в какой-либо другой библиотеке, которая не находится под ваш контроль. Эта библиотека может использовать другие методы файлового объекта.
Проверьте этот способ, когда я все еще позволяю всем делать stderr/stdout (или любой файл в этом отношении), а также отправлять сообщение в файл журнала с помощью средства ведения журнала Python (но вы действительно можете что-то сделать с этим):
class FileToLogInterface(file):
'''
Interface to make sure that everytime anything is written to stderr, it is
also forwarded to a file.
'''
def __init__(self, *args, **kwargs):
if 'cfg' not in kwargs:
raise TypeError('argument cfg is required.')
else:
if not isinstance(kwargs['cfg'], config.Config):
raise TypeError(
'argument cfg should be a valid '
'PostSegmentation configuration object i.e. '
'postsegmentation.config.Config')
self._cfg = kwargs['cfg']
kwargs.pop('cfg')
self._logger = logging.getlogger('access_log')
super(FileToLogInterface, self).__init__(*args, **kwargs)
def write(self, msg):
super(FileToLogInterface, self).write(msg)
self._logger.info(msg)
Ответ 9
Вот вариант ответа Yuda Prawira:
- реализовать
flush()
и все атрибуты файла - напишите это как контекстный менеджер
- захватить
stderr
также
,
import contextlib, sys
@contextlib.contextmanager
def log_print(file):
# capture all outputs to a log file while still printing it
class Logger:
def __init__(self, file):
self.terminal = sys.stdout
self.log = file
def write(self, message):
self.terminal.write(message)
self.log.write(message)
def __getattr__(self, attr):
return getattr(self.terminal, attr)
logger = Logger(file)
_stdout = sys.stdout
_stderr = sys.stderr
sys.stdout = logger
sys.stderr = logger
try:
yield logger.log
finally:
sys.stdout = _stdout
sys.stderr = _stderr
with log_print(open('mylogfile.log', 'w')):
print('hello world')
print('hello world on stderr', file=sys.stderr)
# you can capture the output to a string with:
# with log_print(io.StringIO()) as log:
# ....
# print('[captured output]', log.getvalue())
Ответ 10
Программы, написанные на других языках (например, C), должны выполнять специальную магию (называемую двойным форсированием), чтобы отделить от терминала (и предотвратить процессы зомби). Поэтому я считаю, что лучшим решением является имитация ими.
A плюс повторного выполнения вашей программы, вы можете выбрать перенаправления в командной строке, например. /usr/bin/python mycoolscript.py 2>&1 1>/dev/null
Смотрите это сообщение для получения дополнительной информации: В чем причина выполнения двойной вилки при создании демона?
Ответ 11
@marcog
Второй вариант хорош только в том случае, если сценарий исполняется на ходу. Или сценарий должен выполняться полностью только тогда, когда вывод поступает в этот файл и бесконечные циклы не должны присутствовать (оптимально). Лучшее решение, если это простой скрипт.
Ответ 12
Вот и я наконец понял это. Я сделал следующее:
import os
from contextlib import ExitStack, redirect_stdout, redirect_stderr, suppress
from io import StringIO
err = None
with ExitStack() as stack, suppress(Exception) as s3, suppress(ValueError) as s2, suppress(SystemError) as s1:
null_stream = StringIO()
with redirect_stdout(null_stream) as s4:
try:
old = sys.tracebacklimit
sys.tracebacklimit = 0
except:
sys.tracebacklimit = 0
old = 1000
stack.enter_context(redirect_stdout(null_stream))
try:
print('problem code here')
except Exception as errmsg:
err = errmsg
finally:
stack.close()
sys.tracebacklimit = 1000
if err:
raise err
Я отправил все сообщения в StringIO и использую ExitStack()
вместе с контекстом подавления, чтобы остановить отображение чего-либо. Это приводит к проблеме, поэтому в попытке/кроме/наконец я сохранил любую реальную ошибку, и если ее нет, выведите эту ошибку.
Это не отличное решение, но оно работает. У меня нет доступа к C-коду, как меня просили.