Як дублювати sys.stdout у файл журналу?


149

Редагувати: Оскільки виявляється, що рішення немає або я роблю щось настільки нестандартне, що ніхто не знає - я перегляну своє питання, щоб також запитати: Який найкращий спосіб здійснити реєстрацію, коли програма python робить багато системних дзвінків?

У мого додатка є два режими. В інтерактивному режимі я хочу, щоб весь вихід виходив на екран, а також у файл журналу, включаючи вихід з будь-яких системних викликів. У демон-режимі весь вихід надходить у журнал. Режим Daemon чудово працює за допомогою os.dup2(). Я не можу знайти спосіб "підключити" весь вихід до журналу в інтерактивному режимі, не змінюючи кожного системного виклику.


Іншими словами, я хочу функціонувати командний рядок 'tee' для будь-якого виводу, згенерованого програмою python, включаючи вихід системного виклику .

Для уточнення:

Щоб перенаправити весь результат, я роблю щось подібне, і він чудово працює:

# open our log file
so = se = open("%s.log" % self.name, 'w', 0)

# re-open stdout without buffering
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

# redirect stdout and stderr to the log file opened above
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())

Приємно в цьому те, що він не вимагає спеціальних дзвінків для друку з решти коду. Код також виконує деякі команди оболонки, тому непогано також не мати справу з кожним їх результатом.

Просто я хочу зробити те саме, за винятком дублювання замість перенаправлення.

Спочатку подумав, що я подумав, що просто змінити рухи dup2слід. Чому ні? Ось мій тест:

import os, sys

### my broken solution:
so = se = open("a.log", 'w', 0)
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

os.dup2(sys.stdout.fileno(), so.fileno())
os.dup2(sys.stderr.fileno(), se.fileno())
###

print("foo bar")

os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {})
os.execve("/bin/ls", ["/bin/ls"], os.environ)

Файл "a.log" повинен бути ідентичним тому, що було відображено на екрані.


Якщо ви переглянете сторінку man ( manpagez.com/man/2/dup2 ), 2-й аргумент до dup2 завжди закритий (якщо він уже відкритий). Тож у вашому "розбитому рішенні" він закривається так і ось, а потім переназначає свої файли на sys.stdout.
Джейкоб Габріельсон

1
Re: ваша редакція: це не рідкість, я робив подібні кілька разів (в інших мовах). У той час як Unix дозволить кілька "псевдонімів" для однієї ручки файлу, він не "розділить" файлову ручку (скопіюйте її на кілька інших). Тож вам доведеться реалізувати «трійник» самостійно (або просто використовувати «трійник», дивіться мою грубу відповідь).
Джейкоб Габріельсон

Я думаю, що відповідь JohnT краща за фактично прийняту. Ви можете змінити прийняту відповідь.
Phong

"Я роблю щось настільки нестандартне" - ви насправді є, люди просто надсилають свої журнали до більш жорстких і мають справу з командного рядка.
хачик

Відповіді:


55

Оскільки вам зручно нерестувати зовнішні процеси зі свого коду, ви можете використовувати teeсебе. Я не знаю жодного виклику системи Unix, який би робив саме те, що teeробить.

# Note this version was written circa Python 2.6, see below for
# an updated 3.3+-compatible version.
import subprocess, os, sys

# Unbuffer output (this ensures the output is in the correct order)
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

tee = subprocess.Popen(["tee", "log.txt"], stdin=subprocess.PIPE)
os.dup2(tee.stdin.fileno(), sys.stdout.fileno())
os.dup2(tee.stdin.fileno(), sys.stderr.fileno())

print "\nstdout"
print >>sys.stderr, "stderr"
os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {})
os.execve("/bin/ls", ["/bin/ls"], os.environ)

Ви також можете емулювати teeза допомогою багатопроцесорного пакету (або використовувати обробку, якщо ви використовуєте Python 2.5 або новіші версії).

Оновлення

Ось версія, сумісна з Python 3.3 +:

import subprocess, os, sys

tee = subprocess.Popen(["tee", "log.txt"], stdin=subprocess.PIPE)
# Cause tee's stdin to get a copy of our stdin/stdout (as well as that
# of any child processes we spawn)
os.dup2(tee.stdin.fileno(), sys.stdout.fileno())
os.dup2(tee.stdin.fileno(), sys.stderr.fileno())

# The flush flag is needed to guarantee these lines are written before
# the two spawned /bin/ls processes emit any output
print("\nstdout", flush=True)
print("stderr", file=sys.stderr, flush=True)

# These child processes' stdin/stdout are 
os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {})
os.execve("/bin/ls", ["/bin/ls"], os.environ)

28
Ну, ця відповідь спрацює, тому я прийму її. Все-таки це змушує мене почуватися брудним.
drue

2
Я щойно опублікував чисту реалізацію python tee (сумісний з py2 / 3), який може працювати на будь-якій платформі, а також використовуватись у різних конфігураціях журналу. stackoverflow.com/questions/616645/…
sorin

8
Якщо Python працює на одній з моїх машин, а рішення не відповідає, це не є пітонічним рішенням. Через це відхилився.
anatoly techtonik

2
Відповідно до цієї публікації,sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) з пітона 3.3 (див. PEP 3116) лінія більше не працює
Кен Майєрс

1
Отримала помилку "sys: 1: ResourceWarning: unclosed file <_io.BufferedWriter name = 5>", тому мені довелося додати tee.stdin.close()в кінці програми. Я також отримую "ResourceWarning: підпроцес 1842 все ще працює", і додавання sys.stdout.close(); sys.stderr.close()в кінці програми виправляє це.
Матьє

136

У мене була ця сама проблема раніше, і цей фрагмент був дуже корисним:

class Tee(object):
    def __init__(self, name, mode):
        self.file = open(name, mode)
        self.stdout = sys.stdout
        sys.stdout = self
    def __del__(self):
        sys.stdout = self.stdout
        self.file.close()
    def write(self, data):
        self.file.write(data)
        self.stdout.write(data)
    def flush(self):
        self.file.flush()

від: http://mail.python.org/pipermail/python-list/2007-May/438106.html


7
+1 для внутрішньої обробки переназначення sys.stdout, щоб можна було закінчити ведення журналу, видаливши об’єкт Tee
Бен Бланк

12
Я б до цього додав рум'янець. Напр .: 'self.file.flush ()'
Люк Стенлі

4
Я не згоден з модулем реєстрації. Відмінно підходить для декількох хитрощів. Ведення журналів занадто велике для цього.
Кобор42

4
Не забудьте зазначити переглянуту версію в цьому подальшому кроці на пов’язане обговорення у відповіді.
мартіно

4
Це НЕ буде працювати. __del__не викликається до закінчення виконання. Дивіться stackoverflow.com/questions/6104535/…
Nux

77

Оператор printвикликає write()метод будь-якого об'єкта, який ви присвоюєте sys.stdout.

Я б розкрутив невеликий клас, щоб писати відразу в два місця ...

import sys

class Logger(object):
    def __init__(self):
        self.terminal = sys.stdout
        self.log = open("log.dat", "a")

    def write(self, message):
        self.terminal.write(message)
        self.log.write(message)  

sys.stdout = Logger()

Тепер printвислів буде перегукуватися на екрані та додаватись до вашого журналу:

# prints "1 2" to <stdout> AND log.dat
print "%d %d" % (1,2)

Це, очевидно, швидко і брудно. Деякі примітки:

  • Ймовірно, вам слід параметизувати ім’я файлу журналу.
  • Ви, ймовірно, повинні повернути sys.stdout, <stdout>якщо ви не будете вести журнал протягом тривалості програми.
  • Вам може знадобитися можливість запису в декілька файлів журналу одночасно або обробляти різні рівні журналу тощо.

Це все досить просто, що мені зручно залишати їх як вправи для читача. Основне розуміння тут полягає в тому, що printпросто викликає "файлоподібний об'єкт", якому призначено sys.stdout.


Саме те, що я збирався опублікувати, в значній мірі. +1, коли ви вирішите проблему з записом, не маючи власного аргументу. Крім того, було б краще, щоб той файл, який ви збираєтеся записати, передавався в пекло. Пекло, може бути також кращим дизайном, щоб передали stdout.
Девін Жанп'єр

@ Девін, так це було швидко і брудно, я занотую кілька можливих попередніх вдосконалень.
Триптих

7
Цю відповідь я вибрав занадто рано. Він чудово підходить для "друку", але не настільки для зовнішнього виведення команд.
drue

2
Клас Logger також повинен визначати метод flush (), такий як "def flush (): self.terminal.flush (); self.log.flush ()"
blokeley

5
Ви кажете The print statement will call the write() method of any object you assign to sys.stdout. А як щодо інших функцій, що передають дані в stdout, не використовуючи print. Наприклад, якщо я створю процес, використовуючи subprocess.callйого вихід, йде до консолі, але не до log.datфайлу ... чи існує спосіб виправити це?
jpo38

64

Те, що вам справді хочеться - це loggingмодуль зі стандартної бібліотеки. Створіть реєстратор і приєднайте два обробники, один буде записом у файл, а другий у stdout або stderr.

Див Logging кілька адресатів для отримання докладної інформації


9
Модуль реєстрації не записує винятки та інші важливі результати для stdout, що може бути корисно при аналізі журналів на сервері збірки (наприклад).
anatoly techtonik

2
loggingмодуль не переспрямовує вихід із системних викликів, таких якos.write(1, b'stdout')
jfs

17

Ось ще одне рішення, яке є більш загальним, ніж інші - воно підтримує розділення виводу (записане в sys.stdout) на будь-яку кількість файлоподібних об'єктів. Немає жодної вимоги, що __stdout__вона включається.

import sys

class multifile(object):
    def __init__(self, files):
        self._files = files
    def __getattr__(self, attr, *args):
        return self._wrap(attr, *args)
    def _wrap(self, attr, *args):
        def g(*a, **kw):
            for f in self._files:
                res = getattr(f, attr, *args)(*a, **kw)
            return res
        return g

# for a tee-like behavior, use like this:
sys.stdout = multifile([ sys.stdout, open('myfile.txt', 'w') ])

# all these forms work:
print 'abc'
print >>sys.stdout, 'line2'
sys.stdout.write('line3\n')

ПРИМІТКА. Це підтвердження концепції. Реалізація тут не є завершеною, оскільки вона обертає лише методи файлоподібних об'єктів (наприклад,write файлоподібних ), не залишаючи членів / властивостей / setattr і т.д.

Що мені подобається про це, крім своєї спільності, є те , що він чистий в тому сенсі , що не чинить ніяких прямих викликів write, flush, os.dup2і т.д.


3
Я мав би init take * файли не файли, але в іншому випадку, так, це. Жодне з інших рішень не ізолює функцію "трійника", не намагаючись вирішити інші проблеми. Якщо ви хочете поставити префікс на все, що виводиться, ви можете перенести цей клас у клас запису-префікса. (Якщо ви хочете поставити префікс лише на один потік, ви загортаєте потік і передаєте його цьому класу.) Цей також має перевагу в тому, що мультифайл ([]) створює файл, який ігнорує все (наприклад, open ('/ dev /нуль')).
Бен

Чому взагалі _wrapтут? Не можете ви скопіювати код там, __getattr__і він працює так само?
тимотрей

@Ben насправді multifile([])створює файл, який відкриває кожен UnboundLocalErrorраз, коли ви викликаєте один із його методів. ( resповертається без призначення)
timotree

13

Як описано в іншому місці, можливо, найкращим рішенням є використання модуля журналу безпосередньо:

import logging

logging.basicConfig(level=logging.DEBUG, filename='mylog.log')
logging.info('this should to write to the log file')

Однак є деякі (рідкісні) випадки, коли ви дуже хочете перенаправляти stdout. У мене виникла така ситуація, коли я розширював команду runserver django, яка використовує print: я не хотів зламати джерело django, але мені знадобилося твердження про друк, щоб перейти до файлу.

Це спосіб перенаправити stdout і stderr від оболонки за допомогою модуля реєстрації:

import logging, sys

class LogFile(object):
    """File-like object to log text using the `logging` module."""

    def __init__(self, name=None):
        self.logger = logging.getLogger(name)

    def write(self, msg, level=logging.INFO):
        self.logger.log(level, msg)

    def flush(self):
        for handler in self.logger.handlers:
            handler.flush()

logging.basicConfig(level=logging.DEBUG, filename='mylog.log')

# Redirect stdout and stderr
sys.stdout = LogFile('stdout')
sys.stderr = LogFile('stderr')

print 'this should to write to the log file'

Ви повинні використовувати цю реалізацію LogFile лише в тому випадку, якщо ви дійсно не можете використовувати модуль журналу безпосередньо.


11

Я написав tee()реалізацію в Python, яка повинна працювати в більшості випадків, і вона працює і в Windows.

https://github.com/pycontribs/tendo

Крім того, ви можете використовувати його в поєднанні з loggingмодулем від Python, якщо хочете.


Хм - це посилання більше не працює - де його ще можна знайти?
Danny Staple

1
Нічого, ваш пакунок зграє, особливо якщо ви знаєте, наскільки громіздка культура консолі Windows, але не здавались, щоб вона працювала!
n611x007

8

(Ах, просто перечитайте своє запитання і переконайтеся, що це не зовсім стосується.)

Ось прикладна програма, яка використовує модуль реєстрації python . Цей модуль реєстрації журналу використовується у всіх версіях з 2.3. У цьому зразку ведення журналу можна настроїти за допомогою параметрів командного рядка.

У цілком режимі він буде входити лише у файл, у звичайному режимі він увійде як у файл, так і в консоль.

import os
import sys
import logging
from optparse import OptionParser

def initialize_logging(options):
    """ Log information based upon users options"""

    logger = logging.getLogger('project')
    formatter = logging.Formatter('%(asctime)s %(levelname)s\t%(message)s')
    level = logging.__dict__.get(options.loglevel.upper(),logging.DEBUG)
    logger.setLevel(level)

    # Output logging information to screen
    if not options.quiet:
        hdlr = logging.StreamHandler(sys.stderr)
        hdlr.setFormatter(formatter)
        logger.addHandler(hdlr)

    # Output logging information to file
    logfile = os.path.join(options.logdir, "project.log")
    if options.clean and os.path.isfile(logfile):
        os.remove(logfile)
    hdlr2 = logging.FileHandler(logfile)
    hdlr2.setFormatter(formatter)
    logger.addHandler(hdlr2)

    return logger

def main(argv=None):
    if argv is None:
        argv = sys.argv[1:]

    # Setup command line options
    parser = OptionParser("usage: %prog [options]")
    parser.add_option("-l", "--logdir", dest="logdir", default=".", help="log DIRECTORY (default ./)")
    parser.add_option("-v", "--loglevel", dest="loglevel", default="debug", help="logging level (debug, info, error)")
    parser.add_option("-q", "--quiet", action="store_true", dest="quiet", help="do not log to console")
    parser.add_option("-c", "--clean", dest="clean", action="store_true", default=False, help="remove old log file")

    # Process command line options
    (options, args) = parser.parse_args(argv)

    # Setup logger format and output locations
    logger = initialize_logging(options)

    # Examples
    logger.error("This is an error message.")
    logger.info("This is an info message.")
    logger.debug("This is a debug message.")

if __name__ == "__main__":
    sys.exit(main())

Хороша відповідь. Я побачив кілька дійсно складних способів реплікації входу в консоль, але створення StreamHandler з stderr - це відповідь, яку я шукав :)
meatvest

Код приємно поставити, він не відповідає на питання - це виводить журнал у файл і stderr, оригінальне питання просило дублювати stderr у файл журналу.
emem

8

Щоб заповнити відповідь Джона Т: https://stackoverflow.com/a/616686/395687

Я додав __enter__і __exit__методи використовувати його в якості менеджера контексту з withключовим словом, яке дає цей код

class Tee(object):
    def __init__(self, name, mode):
        self.file = open(name, mode)
        self.stdout = sys.stdout
        sys.stdout = self

    def __del__(self):
        sys.stdout = self.stdout
        self.file.close()

    def write(self, data):
        self.file.write(data)
        self.stdout.write(data)

    def __enter__(self):
        pass

    def __exit__(self, _type, _value, _traceback):
        pass

Потім він може використовуватися як

with Tee('outfile.log', 'w'):
    print('I am written to both stdout and outfile.log')

1
Я б перемістив цю __del__функціональність у__exit__
vontrapp

1
Дійсно, я думаю, що використовувати __del__це погана ідея. Його слід перенести на функцію "закриття", яка викликається __exit__.
cladmi

7

Я знаю, що на це запитання відповідали неодноразово, але для цього я взяв основну відповідь з відповіді Джона Т і змінив її, щоб вона містила запропонований флеш-код, і я дотримувався переглянутої версії. Я також додав введення та вихід, як зазначено у відповіді cladmi для використання з оператором. Крім того, у документації згадується очищення файлів за допомогою, os.fsync()тому я також додав це. Я не знаю, чи вам це справді потрібно, але його є.

import sys, os

class Logger(object):
    "Lumberjack class - duplicates sys.stdout to a log file and it's okay"
    #source: https://stackoverflow.com/q/616645
    def __init__(self, filename="Red.Wood", mode="a", buff=0):
        self.stdout = sys.stdout
        self.file = open(filename, mode, buff)
        sys.stdout = self

    def __del__(self):
        self.close()

    def __enter__(self):
        pass

    def __exit__(self, *args):
        self.close()

    def write(self, message):
        self.stdout.write(message)
        self.file.write(message)

    def flush(self):
        self.stdout.flush()
        self.file.flush()
        os.fsync(self.file.fileno())

    def close(self):
        if self.stdout != None:
            sys.stdout = self.stdout
            self.stdout = None

        if self.file != None:
            self.file.close()
            self.file = None

Потім ви можете використовувати його

with Logger('My_best_girlie_by_my.side'):
    print("we'd sing sing sing")

або

Log=Logger('Sleeps_all.night')
print('works all day')
Log.close()

Багато Thnaks @Status ви вирішили моє запитання ( stackoverflow.com/questions/39143417/… ). Я покладу посилання на ваше рішення.
Мохаммед ЕльНеср

1
@MohammadElNesr Я щойно зрозумів проблему з кодом, коли він використовується із оператором with. Я це виправив і тепер він правильно закривається в кінці блоку.
Статус

1
Для мене це спрацювало чудово, потрібно було лише змінити режим на mode="ab"та у writeфункціїself.file.write(message.encode("utf-8"))
запускає

4

інше рішення за допомогою модуля реєстрації даних:

import logging
import sys

log = logging.getLogger('stdxxx')

class StreamLogger(object):

    def __init__(self, stream, prefix=''):
        self.stream = stream
        self.prefix = prefix
        self.data = ''

    def write(self, data):
        self.stream.write(data)
        self.stream.flush()

        self.data += data
        tmp = str(self.data)
        if '\x0a' in tmp or '\x0d' in tmp:
            tmp = tmp.rstrip('\x0a\x0d')
            log.info('%s%s' % (self.prefix, tmp))
            self.data = ''


logging.basicConfig(level=logging.INFO,
                    filename='text.log',
                    filemode='a')

sys.stdout = StreamLogger(sys.stdout, '[stdout] ')

print 'test for stdout'

3

Жоден із наведених вище відповідей насправді не відповідає проблемі, що поставлена. Я знаю, що це стара тема, але я вважаю, що ця проблема набагато простіша, ніж її роблять усі:

class tee_err(object):

 def __init__(self):
    self.errout = sys.stderr

    sys.stderr = self

    self.log = 'logfile.log'
    log = open(self.log,'w')
    log.close()

 def write(self, line):

    log = open(self.log,'a')
    log.write(line)
    log.close()   

    self.errout.write(line)

Тепер це повторить все до звичайного обробника sys.stderr та вашого файлу. Створіть ще один клас tee_outдля sys.stdout.


2
Аналогічна, краща відповідь була опублікована за два роки до цього: stackoverflow.com/a/616686 . Ваш метод дуже дорогий: кожен виклик на tee=tee_err();tee.write('');tee.write('');...відкриття + закриває файл для кожного write. Див. Stackoverflow.com/q/4867468 та stackoverflow.com/q/164053 для аргументів проти цієї практики.
Роб Ш

3

На прохання @ user5359531 в коментарях під @John T в відповідь , ось копія довідкового поста до переглянутої версії пов'язаного обговорення в цій відповіді:

Issue of redirecting the stdout to both file and screen
Gabriel Genellina gagsl-py2 at yahoo.com.ar
Mon May 28 12:45:51 CEST 2007

    Previous message: Issue of redirecting the stdout to both file and screen
    Next message: Formal interfaces with Python
    Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]

En Mon, 28 May 2007 06:17:39 -0300, 人言落日是天涯,望极天涯不见家
<kelvin.you at gmail.com> escribió:

> I wanna print the log to both the screen and file, so I simulatered a
> 'tee'
>
> class Tee(file):
>
>     def __init__(self, name, mode):
>         file.__init__(self, name, mode)
>         self.stdout = sys.stdout
>         sys.stdout = self
>
>     def __del__(self):
>         sys.stdout = self.stdout
>         self.close()
>
>     def write(self, data):
>         file.write(self, data)
>         self.stdout.write(data)
>
> Tee('logfile', 'w')
> print >>sys.stdout, 'abcdefg'
>
> I found that it only output to the file, nothing to screen. Why?
> It seems the 'write' function was not called when I *print* something.

You create a Tee instance and it is immediately garbage collected. I'd
restore sys.stdout on Tee.close, not __del__ (you forgot to call the
inherited __del__ method, btw).
Mmm, doesn't work. I think there is an optimization somewhere: if it looks
like a real file object, it uses the original file write method, not yours.
The trick would be to use an object that does NOT inherit from file:

import sys
class TeeNoFile(object):
     def __init__(self, name, mode):
         self.file = open(name, mode)
         self.stdout = sys.stdout
         sys.stdout = self
     def close(self):
         if self.stdout is not None:
             sys.stdout = self.stdout
             self.stdout = None
         if self.file is not None:
             self.file.close()
             self.file = None
     def write(self, data):
         self.file.write(data)
         self.stdout.write(data)
     def flush(self):
         self.file.flush()
         self.stdout.flush()
     def __del__(self):
         self.close()

tee=TeeNoFile('logfile', 'w')
print 'abcdefg'
print 'another line'
tee.close()
print 'screen only'
del tee # should do nothing

--
Gabriel Genellina

1

Я пишу сценарій для запуску сценаріїв cmd-рядка. (Оскільки в деяких випадках просто не існує життєздатної заміни команди Linux - наприклад, у випадку rsync.)

Мені дуже хотілося - використовувати механізм реєстрації python за замовчуванням у всіх випадках, коли це можна зробити, але все-таки фіксувати будь-яку помилку, коли щось пішло не так, непередбачене.

Цей код, здається, робить свою справу. Він може бути не особливо елегантним або ефективним (хоча він не використовує string + = string, так що принаймні не має такої потенційної шийки). Я розміщую його на випадок, якщо він дає комусь корисні ідеї.

import logging
import os, sys
import datetime

# Get name of module, use as application name
try:
  ME=os.path.split(__file__)[-1].split('.')[0]
except:
  ME='pyExec_'

LOG_IDENTIFIER="uuu___( o O )___uuu "
LOG_IDR_LENGTH=len(LOG_IDENTIFIER)

class PyExec(object):

  # Use this to capture all possible error / output to log
  class SuperTee(object):
      # Original reference: http://mail.python.org/pipermail/python-list/2007-May/442737.html
      def __init__(self, name, mode):
          self.fl = open(name, mode)
          self.fl.write('\n')
          self.stdout = sys.stdout
          self.stdout.write('\n')
          self.stderr = sys.stderr

          sys.stdout = self
          sys.stderr = self

      def __del__(self):
          self.fl.write('\n')
          self.fl.flush()
          sys.stderr = self.stderr
          sys.stdout = self.stdout
          self.fl.close()

      def write(self, data):
          # If the data to write includes the log identifier prefix, then it is already formatted
          if data[0:LOG_IDR_LENGTH]==LOG_IDENTIFIER:
            self.fl.write("%s\n" % data[LOG_IDR_LENGTH:])
            self.stdout.write(data[LOG_IDR_LENGTH:])

          # Otherwise, we can give it a timestamp
          else:

            timestamp=str(datetime.datetime.now())
            if 'Traceback' == data[0:9]:
              data='%s: %s' % (timestamp, data)
              self.fl.write(data)
            else:
              self.fl.write(data)

            self.stdout.write(data)


  def __init__(self, aName, aCmd, logFileName='', outFileName=''):

    # Using name for 'logger' (context?), which is separate from the module or the function
    baseFormatter=logging.Formatter("%(asctime)s \t %(levelname)s \t %(name)s:%(module)s:%(lineno)d \t %(message)s")
    errorFormatter=logging.Formatter(LOG_IDENTIFIER + "%(asctime)s \t %(levelname)s \t %(name)s:%(module)s:%(lineno)d \t %(message)s")

    if logFileName:
      # open passed filename as append
      fl=logging.FileHandler("%s.log" % aName)
    else:
      # otherwise, use log filename as a one-time use file
      fl=logging.FileHandler("%s.log" % aName, 'w')

    fl.setLevel(logging.DEBUG)
    fl.setFormatter(baseFormatter)

    # This will capture stdout and CRITICAL and beyond errors

    if outFileName:
      teeFile=PyExec.SuperTee("%s_out.log" % aName)
    else:
      teeFile=PyExec.SuperTee("%s_out.log" % aName, 'w')

    fl_out=logging.StreamHandler( teeFile )
    fl_out.setLevel(logging.CRITICAL)
    fl_out.setFormatter(errorFormatter)

    # Set up logging
    self.log=logging.getLogger('pyExec_main')
    log=self.log

    log.addHandler(fl)
    log.addHandler(fl_out)

    print "Test print statement."

    log.setLevel(logging.DEBUG)

    log.info("Starting %s", ME)
    log.critical("Critical.")

    # Caught exception
    try:
      raise Exception('Exception test.')
    except Exception,e:
      log.exception(str(e))

    # Uncaught exception
    a=2/0


PyExec('test_pyExec',None)

Очевидно, якщо ви не настільки примхливі, як я, замініть LOG_IDENTIFIER на інший рядок, який вам не подобається бачити, як хтось пише в журнал.


0

Якщо ви хочете записати весь вихід у файл І вивести його в текстовий файл, ви можете зробити наступне. Це трохи хакі, але це працює:

import logging
debug = input("Debug or not")
if debug == "1":
    logging.basicConfig(level=logging.DEBUG, filename='./OUT.txt')
    old_print = print
    def print(string):
        old_print(string)
        logging.info(string)
print("OMG it works!")

EDIT: Зауважте, що це не записує помилки, якщо ви не перенаправляєте sys.stderr на sys.stdout

EDIT2: Друге питання полягає в тому, що вам потрібно пройти 1 аргумент на відміну від вбудованої функції.

EDIT3: Перегляньте код перед тим, як записати stdin та stdout на консоль та подати файл із stderr лише у файл

import logging, sys
debug = input("Debug or not")
if debug == "1":
    old_input = input
    sys.stderr.write = logging.info
    def input(string=""):
        string_in = old_input(string)
        logging.info("STRING IN " + string_in)
        return string_in
    logging.basicConfig(level=logging.DEBUG, filename='./OUT.txt')
    old_print = print
    def print(string="", string2=""):
        old_print(string, string2)
        logging.info(string)
        logging.info(string2)
print("OMG")
b = input()
print(a) ## Deliberate error for testing

-1

Я написав повну заміну sys.stderrі просто дублюється код перейменування stderrдля , stdoutщоб зробити його також можна замінитиsys.stdout .

Для цього я створюю той же тип об'єкта , як поточні stderrі stdout, і вперед все методи до вихідної системи stderrі stdout:

import os
import sys
import logging

class StdErrReplament(object):
    """
        How to redirect stdout and stderr to logger in Python
        /programming/19425736/how-to-redirect-stdout-and-stderr-to-logger-in-python

        Set a Read-Only Attribute in Python?
        /programming/24497316/set-a-read-only-attribute-in-python
    """
    is_active = False

    @classmethod
    def lock(cls, logger):
        """
            Attach this singleton logger to the `sys.stderr` permanently.
        """
        global _stderr_singleton
        global _stderr_default
        global _stderr_default_class_type

        # On Sublime Text, `sys.__stderr__` is set to None, because they already replaced `sys.stderr`
        # by some `_LogWriter()` class, then just save the current one over there.
        if not sys.__stderr__:
            sys.__stderr__ = sys.stderr

        try:
            _stderr_default
            _stderr_default_class_type

        except NameError:
            _stderr_default = sys.stderr
            _stderr_default_class_type = type( _stderr_default )

        # Recreate the sys.stderr logger when it was reset by `unlock()`
        if not cls.is_active:
            cls.is_active = True
            _stderr_write = _stderr_default.write

            logger_call = logger.debug
            clean_formatter = logger.clean_formatter

            global _sys_stderr_write
            global _sys_stderr_write_hidden

            if sys.version_info <= (3,2):
                logger.file_handler.terminator = '\n'

            # Always recreate/override the internal write function used by `_sys_stderr_write`
            def _sys_stderr_write_hidden(*args, **kwargs):
                """
                    Suppress newline in Python logging module
                    /programming/7168790/suppress-newline-in-python-logging-module
                """

                try:
                    _stderr_write( *args, **kwargs )
                    file_handler = logger.file_handler

                    formatter = file_handler.formatter
                    terminator = file_handler.terminator

                    file_handler.formatter = clean_formatter
                    file_handler.terminator = ""

                    kwargs['extra'] = {'_duplicated_from_file': True}
                    logger_call( *args, **kwargs )

                    file_handler.formatter = formatter
                    file_handler.terminator = terminator

                except Exception:
                    logger.exception( "Could not write to the file_handler: %s(%s)", file_handler, logger )
                    cls.unlock()

            # Only create one `_sys_stderr_write` function pointer ever
            try:
                _sys_stderr_write

            except NameError:

                def _sys_stderr_write(*args, **kwargs):
                    """
                        Hides the actual function pointer. This allow the external function pointer to
                        be cached while the internal written can be exchanged between the standard
                        `sys.stderr.write` and our custom wrapper around it.
                    """
                    _sys_stderr_write_hidden( *args, **kwargs )

        try:
            # Only create one singleton instance ever
            _stderr_singleton

        except NameError:

            class StdErrReplamentHidden(_stderr_default_class_type):
                """
                    Which special methods bypasses __getattribute__ in Python?
                    /programming/12872695/which-special-methods-bypasses-getattribute-in-python
                """

                if hasattr( _stderr_default, "__abstractmethods__" ):
                    __abstractmethods__ = _stderr_default.__abstractmethods__

                if hasattr( _stderr_default, "__base__" ):
                    __base__ = _stderr_default.__base__

                if hasattr( _stderr_default, "__bases__" ):
                    __bases__ = _stderr_default.__bases__

                if hasattr( _stderr_default, "__basicsize__" ):
                    __basicsize__ = _stderr_default.__basicsize__

                if hasattr( _stderr_default, "__call__" ):
                    __call__ = _stderr_default.__call__

                if hasattr( _stderr_default, "__class__" ):
                    __class__ = _stderr_default.__class__

                if hasattr( _stderr_default, "__delattr__" ):
                    __delattr__ = _stderr_default.__delattr__

                if hasattr( _stderr_default, "__dict__" ):
                    __dict__ = _stderr_default.__dict__

                if hasattr( _stderr_default, "__dictoffset__" ):
                    __dictoffset__ = _stderr_default.__dictoffset__

                if hasattr( _stderr_default, "__dir__" ):
                    __dir__ = _stderr_default.__dir__

                if hasattr( _stderr_default, "__doc__" ):
                    __doc__ = _stderr_default.__doc__

                if hasattr( _stderr_default, "__eq__" ):
                    __eq__ = _stderr_default.__eq__

                if hasattr( _stderr_default, "__flags__" ):
                    __flags__ = _stderr_default.__flags__

                if hasattr( _stderr_default, "__format__" ):
                    __format__ = _stderr_default.__format__

                if hasattr( _stderr_default, "__ge__" ):
                    __ge__ = _stderr_default.__ge__

                if hasattr( _stderr_default, "__getattribute__" ):
                    __getattribute__ = _stderr_default.__getattribute__

                if hasattr( _stderr_default, "__gt__" ):
                    __gt__ = _stderr_default.__gt__

                if hasattr( _stderr_default, "__hash__" ):
                    __hash__ = _stderr_default.__hash__

                if hasattr( _stderr_default, "__init__" ):
                    __init__ = _stderr_default.__init__

                if hasattr( _stderr_default, "__init_subclass__" ):
                    __init_subclass__ = _stderr_default.__init_subclass__

                if hasattr( _stderr_default, "__instancecheck__" ):
                    __instancecheck__ = _stderr_default.__instancecheck__

                if hasattr( _stderr_default, "__itemsize__" ):
                    __itemsize__ = _stderr_default.__itemsize__

                if hasattr( _stderr_default, "__le__" ):
                    __le__ = _stderr_default.__le__

                if hasattr( _stderr_default, "__lt__" ):
                    __lt__ = _stderr_default.__lt__

                if hasattr( _stderr_default, "__module__" ):
                    __module__ = _stderr_default.__module__

                if hasattr( _stderr_default, "__mro__" ):
                    __mro__ = _stderr_default.__mro__

                if hasattr( _stderr_default, "__name__" ):
                    __name__ = _stderr_default.__name__

                if hasattr( _stderr_default, "__ne__" ):
                    __ne__ = _stderr_default.__ne__

                if hasattr( _stderr_default, "__new__" ):
                    __new__ = _stderr_default.__new__

                if hasattr( _stderr_default, "__prepare__" ):
                    __prepare__ = _stderr_default.__prepare__

                if hasattr( _stderr_default, "__qualname__" ):
                    __qualname__ = _stderr_default.__qualname__

                if hasattr( _stderr_default, "__reduce__" ):
                    __reduce__ = _stderr_default.__reduce__

                if hasattr( _stderr_default, "__reduce_ex__" ):
                    __reduce_ex__ = _stderr_default.__reduce_ex__

                if hasattr( _stderr_default, "__repr__" ):
                    __repr__ = _stderr_default.__repr__

                if hasattr( _stderr_default, "__setattr__" ):
                    __setattr__ = _stderr_default.__setattr__

                if hasattr( _stderr_default, "__sizeof__" ):
                    __sizeof__ = _stderr_default.__sizeof__

                if hasattr( _stderr_default, "__str__" ):
                    __str__ = _stderr_default.__str__

                if hasattr( _stderr_default, "__subclasscheck__" ):
                    __subclasscheck__ = _stderr_default.__subclasscheck__

                if hasattr( _stderr_default, "__subclasses__" ):
                    __subclasses__ = _stderr_default.__subclasses__

                if hasattr( _stderr_default, "__subclasshook__" ):
                    __subclasshook__ = _stderr_default.__subclasshook__

                if hasattr( _stderr_default, "__text_signature__" ):
                    __text_signature__ = _stderr_default.__text_signature__

                if hasattr( _stderr_default, "__weakrefoffset__" ):
                    __weakrefoffset__ = _stderr_default.__weakrefoffset__

                if hasattr( _stderr_default, "mro" ):
                    mro = _stderr_default.mro

                def __init__(self):
                    """
                        Override any super class `type( _stderr_default )` constructor, so we can 
                        instantiate any kind of `sys.stderr` replacement object, in case it was already 
                        replaced by something else like on Sublime Text with `_LogWriter()`.

                        Assures all attributes were statically replaced just above. This should happen in case
                        some new attribute is added to the python language.

                        This also ignores the only two methods which are not equal, `__init__()` and `__getattribute__()`.
                    """
                    different_methods = ("__init__", "__getattribute__")
                    attributes_to_check = set( dir( object ) + dir( type ) )

                    for attribute in attributes_to_check:

                        if attribute not in different_methods \
                                and hasattr( _stderr_default, attribute ):

                            base_class_attribute = super( _stderr_default_class_type, self ).__getattribute__( attribute )
                            target_class_attribute = _stderr_default.__getattribute__( attribute )

                            if base_class_attribute != target_class_attribute:
                                sys.stderr.write( "    The base class attribute `%s` is different from the target class:\n%s\n%s\n\n" % (
                                        attribute, base_class_attribute, target_class_attribute ) )

                def __getattribute__(self, item):

                    if item == 'write':
                        return _sys_stderr_write

                    try:
                        return _stderr_default.__getattribute__( item )

                    except AttributeError:
                        return super( _stderr_default_class_type, _stderr_default ).__getattribute__( item )

            _stderr_singleton = StdErrReplamentHidden()
            sys.stderr = _stderr_singleton

        return cls

    @classmethod
    def unlock(cls):
        """
            Detach this `stderr` writer from `sys.stderr` and allow the next call to `lock()` create
            a new writer for the stderr.
        """

        if cls.is_active:
            global _sys_stderr_write_hidden

            cls.is_active = False
            _sys_stderr_write_hidden = _stderr_default.write



class StdOutReplament(object):
    """
        How to redirect stdout and stderr to logger in Python
        /programming/19425736/how-to-redirect-stdout-and-stderr-to-logger-in-python

        Set a Read-Only Attribute in Python?
        /programming/24497316/set-a-read-only-attribute-in-python
    """
    is_active = False

    @classmethod
    def lock(cls, logger):
        """
            Attach this singleton logger to the `sys.stdout` permanently.
        """
        global _stdout_singleton
        global _stdout_default
        global _stdout_default_class_type

        # On Sublime Text, `sys.__stdout__` is set to None, because they already replaced `sys.stdout`
        # by some `_LogWriter()` class, then just save the current one over there.
        if not sys.__stdout__:
            sys.__stdout__ = sys.stdout

        try:
            _stdout_default
            _stdout_default_class_type

        except NameError:
            _stdout_default = sys.stdout
            _stdout_default_class_type = type( _stdout_default )

        # Recreate the sys.stdout logger when it was reset by `unlock()`
        if not cls.is_active:
            cls.is_active = True
            _stdout_write = _stdout_default.write

            logger_call = logger.debug
            clean_formatter = logger.clean_formatter

            global _sys_stdout_write
            global _sys_stdout_write_hidden

            if sys.version_info <= (3,2):
                logger.file_handler.terminator = '\n'

            # Always recreate/override the internal write function used by `_sys_stdout_write`
            def _sys_stdout_write_hidden(*args, **kwargs):
                """
                    Suppress newline in Python logging module
                    /programming/7168790/suppress-newline-in-python-logging-module
                """

                try:
                    _stdout_write( *args, **kwargs )
                    file_handler = logger.file_handler

                    formatter = file_handler.formatter
                    terminator = file_handler.terminator

                    file_handler.formatter = clean_formatter
                    file_handler.terminator = ""

                    kwargs['extra'] = {'_duplicated_from_file': True}
                    logger_call( *args, **kwargs )

                    file_handler.formatter = formatter
                    file_handler.terminator = terminator

                except Exception:
                    logger.exception( "Could not write to the file_handler: %s(%s)", file_handler, logger )
                    cls.unlock()

            # Only create one `_sys_stdout_write` function pointer ever
            try:
                _sys_stdout_write

            except NameError:

                def _sys_stdout_write(*args, **kwargs):
                    """
                        Hides the actual function pointer. This allow the external function pointer to
                        be cached while the internal written can be exchanged between the standard
                        `sys.stdout.write` and our custom wrapper around it.
                    """
                    _sys_stdout_write_hidden( *args, **kwargs )

        try:
            # Only create one singleton instance ever
            _stdout_singleton

        except NameError:

            class StdOutReplamentHidden(_stdout_default_class_type):
                """
                    Which special methods bypasses __getattribute__ in Python?
                    /programming/12872695/which-special-methods-bypasses-getattribute-in-python
                """

                if hasattr( _stdout_default, "__abstractmethods__" ):
                    __abstractmethods__ = _stdout_default.__abstractmethods__

                if hasattr( _stdout_default, "__base__" ):
                    __base__ = _stdout_default.__base__

                if hasattr( _stdout_default, "__bases__" ):
                    __bases__ = _stdout_default.__bases__

                if hasattr( _stdout_default, "__basicsize__" ):
                    __basicsize__ = _stdout_default.__basicsize__

                if hasattr( _stdout_default, "__call__" ):
                    __call__ = _stdout_default.__call__

                if hasattr( _stdout_default, "__class__" ):
                    __class__ = _stdout_default.__class__

                if hasattr( _stdout_default, "__delattr__" ):
                    __delattr__ = _stdout_default.__delattr__

                if hasattr( _stdout_default, "__dict__" ):
                    __dict__ = _stdout_default.__dict__

                if hasattr( _stdout_default, "__dictoffset__" ):
                    __dictoffset__ = _stdout_default.__dictoffset__

                if hasattr( _stdout_default, "__dir__" ):
                    __dir__ = _stdout_default.__dir__

                if hasattr( _stdout_default, "__doc__" ):
                    __doc__ = _stdout_default.__doc__

                if hasattr( _stdout_default, "__eq__" ):
                    __eq__ = _stdout_default.__eq__

                if hasattr( _stdout_default, "__flags__" ):
                    __flags__ = _stdout_default.__flags__

                if hasattr( _stdout_default, "__format__" ):
                    __format__ = _stdout_default.__format__

                if hasattr( _stdout_default, "__ge__" ):
                    __ge__ = _stdout_default.__ge__

                if hasattr( _stdout_default, "__getattribute__" ):
                    __getattribute__ = _stdout_default.__getattribute__

                if hasattr( _stdout_default, "__gt__" ):
                    __gt__ = _stdout_default.__gt__

                if hasattr( _stdout_default, "__hash__" ):
                    __hash__ = _stdout_default.__hash__

                if hasattr( _stdout_default, "__init__" ):
                    __init__ = _stdout_default.__init__

                if hasattr( _stdout_default, "__init_subclass__" ):
                    __init_subclass__ = _stdout_default.__init_subclass__

                if hasattr( _stdout_default, "__instancecheck__" ):
                    __instancecheck__ = _stdout_default.__instancecheck__

                if hasattr( _stdout_default, "__itemsize__" ):
                    __itemsize__ = _stdout_default.__itemsize__

                if hasattr( _stdout_default, "__le__" ):
                    __le__ = _stdout_default.__le__

                if hasattr( _stdout_default, "__lt__" ):
                    __lt__ = _stdout_default.__lt__

                if hasattr( _stdout_default, "__module__" ):
                    __module__ = _stdout_default.__module__

                if hasattr( _stdout_default, "__mro__" ):
                    __mro__ = _stdout_default.__mro__

                if hasattr( _stdout_default, "__name__" ):
                    __name__ = _stdout_default.__name__

                if hasattr( _stdout_default, "__ne__" ):
                    __ne__ = _stdout_default.__ne__

                if hasattr( _stdout_default, "__new__" ):
                    __new__ = _stdout_default.__new__

                if hasattr( _stdout_default, "__prepare__" ):
                    __prepare__ = _stdout_default.__prepare__

                if hasattr( _stdout_default, "__qualname__" ):
                    __qualname__ = _stdout_default.__qualname__

                if hasattr( _stdout_default, "__reduce__" ):
                    __reduce__ = _stdout_default.__reduce__

                if hasattr( _stdout_default, "__reduce_ex__" ):
                    __reduce_ex__ = _stdout_default.__reduce_ex__

                if hasattr( _stdout_default, "__repr__" ):
                    __repr__ = _stdout_default.__repr__

                if hasattr( _stdout_default, "__setattr__" ):
                    __setattr__ = _stdout_default.__setattr__

                if hasattr( _stdout_default, "__sizeof__" ):
                    __sizeof__ = _stdout_default.__sizeof__

                if hasattr( _stdout_default, "__str__" ):
                    __str__ = _stdout_default.__str__

                if hasattr( _stdout_default, "__subclasscheck__" ):
                    __subclasscheck__ = _stdout_default.__subclasscheck__

                if hasattr( _stdout_default, "__subclasses__" ):
                    __subclasses__ = _stdout_default.__subclasses__

                if hasattr( _stdout_default, "__subclasshook__" ):
                    __subclasshook__ = _stdout_default.__subclasshook__

                if hasattr( _stdout_default, "__text_signature__" ):
                    __text_signature__ = _stdout_default.__text_signature__

                if hasattr( _stdout_default, "__weakrefoffset__" ):
                    __weakrefoffset__ = _stdout_default.__weakrefoffset__

                if hasattr( _stdout_default, "mro" ):
                    mro = _stdout_default.mro

                def __init__(self):
                    """
                        Override any super class `type( _stdout_default )` constructor, so we can 
                        instantiate any kind of `sys.stdout` replacement object, in case it was already 
                        replaced by something else like on Sublime Text with `_LogWriter()`.

                        Assures all attributes were statically replaced just above. This should happen in case
                        some new attribute is added to the python language.

                        This also ignores the only two methods which are not equal, `__init__()` and `__getattribute__()`.
                    """
                    different_methods = ("__init__", "__getattribute__")
                    attributes_to_check = set( dir( object ) + dir( type ) )

                    for attribute in attributes_to_check:

                        if attribute not in different_methods \
                                and hasattr( _stdout_default, attribute ):

                            base_class_attribute = super( _stdout_default_class_type, self ).__getattribute__( attribute )
                            target_class_attribute = _stdout_default.__getattribute__( attribute )

                            if base_class_attribute != target_class_attribute:
                                sys.stdout.write( "    The base class attribute `%s` is different from the target class:\n%s\n%s\n\n" % (
                                        attribute, base_class_attribute, target_class_attribute ) )

                def __getattribute__(self, item):

                    if item == 'write':
                        return _sys_stdout_write

                    try:
                        return _stdout_default.__getattribute__( item )

                    except AttributeError:
                        return super( _stdout_default_class_type, _stdout_default ).__getattribute__( item )

            _stdout_singleton = StdOutReplamentHidden()
            sys.stdout = _stdout_singleton

        return cls

    @classmethod
    def unlock(cls):
        """
            Detach this `stdout` writer from `sys.stdout` and allow the next call to `lock()` create
            a new writer for the stdout.
        """

        if cls.is_active:
            global _sys_stdout_write_hidden

            cls.is_active = False
            _sys_stdout_write_hidden = _stdout_default.write

Для цього можна просто зателефонувати StdErrReplament::lock(logger)та StdOutReplament::lock(logger) передати реєстратор, який ви хочете використовувати для надсилання тексту виводу. Наприклад:

import os
import sys
import logging

current_folder = os.path.dirname( os.path.realpath( __file__ ) )
log_file_path = os.path.join( current_folder, "my_log_file.txt" )

file_handler = logging.FileHandler( log_file_path, 'a' )
file_handler.formatter = logging.Formatter( "%(asctime)s %(name)s %(levelname)s - %(message)s", "%Y-%m-%d %H:%M:%S" )

log = logging.getLogger( __name__ )
log.setLevel( "DEBUG" )
log.addHandler( file_handler )

log.file_handler = file_handler
log.clean_formatter = logging.Formatter( "", "" )

StdOutReplament.lock( log )
StdErrReplament.lock( log )

log.debug( "I am doing usual logging debug..." )
sys.stderr.write( "Tests 1...\n" )
sys.stdout.write( "Tests 2...\n" )

Запустивши цей код, на екрані ви побачите:

введіть тут опис зображення

А про вміст файлу:

введіть тут опис зображення

Якщо ви хочете також бачити вміст log.debugдзвінків на екрані, вам потрібно буде додати обробник потоку до свого реєстратора. У цьому випадку це було б так:

import os
import sys
import logging

class ContextFilter(logging.Filter):
    """ This filter avoids duplicated information to be displayed to the StreamHandler log. """
    def filter(self, record):
        return not "_duplicated_from_file" in record.__dict__

current_folder = os.path.dirname( os.path.realpath( __file__ ) )
log_file_path = os.path.join( current_folder, "my_log_file.txt" )

stream_handler = logging.StreamHandler()
file_handler = logging.FileHandler( log_file_path, 'a' )

formatter = logging.Formatter( "%(asctime)s %(name)s %(levelname)s - %(message)s", "%Y-%m-%d %H:%M:%S" )
file_handler.formatter = formatter
stream_handler.formatter = formatter
stream_handler.addFilter( ContextFilter() )

log = logging.getLogger( __name__ )
log.setLevel( "DEBUG" )
log.addHandler( file_handler )
log.addHandler( stream_handler )

log.file_handler = file_handler
log.stream_handler = stream_handler
log.clean_formatter = logging.Formatter( "", "" )

StdOutReplament.lock( log )
StdErrReplament.lock( log )

log.debug( "I am doing usual logging debug..." )
sys.stderr.write( "Tests 1...\n" )
sys.stdout.write( "Tests 2...\n" )

Що виводиться так під час запуску:

введіть тут опис зображення

Хоча це все ще зберігатиме це у файлі my_log_file.txt:

введіть тут опис зображення

Якщо вимкнути це StdErrReplament:unlock(), він відновить лише стандартну поведінку stderrпотоку, оскільки приєднаний реєстратор не може бути ніколи від'єднаний, оскільки хтось інший може мати посилання на свою стару версію. Ось чому це глобальний сингл, який ніколи не може померти. Тому, у випадку перезавантаження цього модуля impчи чогось іншого, він ніколи не буде відновлювати струм, sys.stderrоскільки він вже був введений в нього, і збереже його внутрішньо.


5
дивовижний рівень випадкової складності дублювання потоку.
Аттіла Лендвай
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.