Вопрос-Ответ

Redirect stdout to a file in Python? [duplicate]

Перенаправить стандартный вывод в файл на Python?

Как мне перенаправить стандартный вывод в произвольный файл на Python?

Когда долго работающий скрипт Python (например, веб-приложение) запускается из сеанса ssh и выполняется в фоновом режиме, а сеанс ssh закрывается, приложение выдает IOError и завершается сбоем в тот момент, когда оно пытается выполнить запись в стандартный вывод. Мне нужно было найти способ заставить приложение и модули выводиться в файл, а не в стандартный вывод, чтобы предотвратить сбой из-за IOError. В настоящее время я использую nohup для перенаправления вывода в файл, и это выполняет работу, но мне было интересно, есть ли способ сделать это без использования nohup, из любопытства.

Я уже пробовал sys.stdout = open('somefile', 'w'), но, похоже, это не мешает некоторым внешним модулям по-прежнему выводить данные на терминал (или, возможно, sys.stdout = ... строка вообще не запускалась). Я знаю, что это должно работать с более простыми скриптами, на которых я тестировал, но у меня также пока не было времени протестировать веб-приложение.

Переведено автоматически
Ответ 1

Если вы хотите выполнить перенаправление в скрипте Python, установка sys.stdout в файловый объект делает свое дело:

# for python3
import sys
with open('file', 'w') as sys.stdout:
print('test')

Гораздо более распространенным методом является использование перенаправления оболочки при выполнении (то же самое в Windows и Linux):

$ python3 foo.py > file
Ответ 2

В Python 3.4 есть contextlib.redirect_stdout() функция+:

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. Последняя версия не является повторно используемой. При желании ее можно переделать.

Он не перенаправляет стандартный вывод на уровне файловых дескрипторов, например:

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

Тот же пример работает и сейчас, если stdout_redirected() используется вместо redirect_stdout():

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

Выходные данные, которые ранее печатались в стандартном выводе, теперь передаются в output.txt пока stdout_redirected() context manager активен.

Примечание: 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 демонизировать свой скрипт и использовать logging module (как предложил @erikb85) вместо print инструкций и просто перенаправлять стандартный вывод для вашего давно работающего скрипта Python, который вы запускаете с помощью 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

Другие ответы не касались случая, когда вы хотите, чтобы разветвленные процессы совместно использовали ваш новый стандартный вывод.

Как это сделать:

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
python