12

Есть python скрипт:

import sys
sys.stdout = open('my_log.log', 'w')
print 'test'

Он пишет весь стандартный вывод в файл. Вопрос: как мне и писать в файл, и одновременно выводить на консоль?

insolor
  • 49,104
nick_gabpe
  • 3,943

4 Answers4

12

Как наколеночное решение, буквально выполняющее задачу, озвученную в вопросе: можно создать класс, который принимает два файловых объекта, и при записи в объект данного класса выполнять запись в оба файловых объекта.

from __future__ import print_function
import sys

class DoubleWrite: def init(self, file1, file2): self.file1 = file1 self.file2 = file2

def write(self, s):
    self.file1.write(s)
    self.file2.write(s)

def flush(self):
    self.file1.flush()
    self.file2.flush()


logfile = open('my_log.log', 'w') sys.stdout = DoubleWrite(sys.stdout, logfile)

print("test")

В целом, для логирования рекомендуется использовать уже существующие решения, в частности модуль logging.

Обновление.

Для корректного перенаправления и последующего восстановления sys.stdout в Python 3 лучше использовать менеджер контекста contextlib.redirect_stdout. В стандартной библиотеке Python 2 такого менеджера контекста, к сожалению, нет.

Пример использования (для Python 3):

import sys
from contextlib import redirect_stdout

class DoubleWrite: ...

with open('my_log.log', 'w') as logfile: with redirect_stdout(DoubleWrite(sys.stdout, logfile)): print('test')

После выхода из блока with redirect_stdout(...) значение sys.stdout восстановится.

insolor
  • 49,104
5

Используя модуль logging:

import sys

from logging import FileHandler import logging

def get_logger(name=file, file='log.txt', encoding='utf-8'): log = logging.getLogger(name) log.setLevel(logging.DEBUG)

# Будут строки вида: "[2017-08-23 09:54:55,356] main.py:34 DEBUG    foo"
# formatter = logging.Formatter('[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s')
formatter = logging.Formatter('%(message)s')

# В файл
fh = FileHandler(file, encoding=encoding)
fh.setFormatter(formatter)
log.addHandler(fh)

# В stdout
sh = logging.StreamHandler(stream=sys.stdout)
sh.setFormatter(formatter)
log.addHandler(sh)

return log


log = get_logger() log.debug('foo') log.debug('bar')

Можно настроить формат вывода, указав в Formatter '[%(asctime)s] %(filename)s:%(lineno)d %(levelname)-8s %(message)s' и получать такие строки:

[2017-08-23 09:54:55,356] main.py:34 DEBUG    foo
[2017-08-23 09:54:55,356] main.py:34 DEBUG    bar
gil9red
  • 77,085
  • 2
    Не стоит import внутрь функции помещать, если нет уважительных причин. Обычно разделяют код, который генерирует события (debug('foo')), от кода, который определяет какого уровня события интересны (level), куда эти события пишутся (StreamHandler) и в каком формате (Formatter). То есть коду, источнику событий, может быть достаточно: logger = logging.getLogger(__name__). А level, handlers, formatters можно хоть из файла читать или в main() добавить basicConfig(level=DEBUG, handlers=[FileHandler('my.log'), StreamHandler()]) – jfs Aug 23 '17 at 06:35
  • 1
    @jfs, рад что вы прокомментировали, т.к. эта тема импорта меня до этого постоянно волновала, и мое мнение такое: если внутрь помещать, функция становится самодостаточной и чтобы она заработала не нужно будет из начала файла копировать импорты (если же какая-то библиотека используется во множестве функций, тогда желательно импорт вынести в начало файла). Насчет указания уровня для Handler'ов согласен -- было лишним. А так, да, возможности настроить логирование масса, но мне нужна была самодостаточная функция создания логера, которую можно было просто скопировать из одного проекта в другой – gil9red Aug 23 '17 at 07:06
  • 1
    Не стоит использовать copy-paste целых функций в качестве инструмента повторного использования кода. Создайте модуль, поместите туда желаемый код (с импортами наверху) и используйте этот модуль где нужно (помещать этот модуль в _vendor папочку или устанавливать через pip -- это уже по обстоятельствам). – jfs Aug 23 '17 at 07:24
  • 1
    @jfs, а почему нет? Если создавать модуль, тогда вместо копипасты функции будет копипаста модуля в разные проекты (через pip не вариант таскать свои костыли/обертки) – gil9red Aug 23 '17 at 07:56
  • попробуйте отдельный вопрос задать, почему модуль является более предпочтительной единицей повторного использования в Питоне по сравнению с copy-paste целых функций – jfs Aug 23 '17 at 08:02
  • @jfs, а вы ответите на тот вопрос? :) – gil9red Aug 23 '17 at 08:05
  • 1
    это не имеет значения. – jfs Aug 23 '17 at 08:36
  • @jfs, спасибо, исправил :) – gil9red Mar 30 '21 at 10:35
3

Проксирует вызов на все содержимое списка

import sys


class CallList(list):
    def __getattr__(self, attr):
        def func(*args, **kwargs):
            for obj in self:
                getattr(obj, attr)(*args,**kwargs)
        return func


cl = CallList([open('file.test', 'w'), sys.stdout])
cl.write("0xDEFACE")
cl.flush()
Andrio Skur
  • 2,873
0xdef
  • 899
2
def echo(file):
    with open(file, 'w') as log:
        while True:
            text = yield
            log.write(text + '\n')
            print(text)

e = echo('log.txt')
next(e)
e.send('123')
e.send('456')
vadim vaduxa
  • 8,897