¿Cómo duplicar sys.stdout en un archivo de registro?


149

Editar: como parece que no hay solución o estoy haciendo algo tan poco estándar que nadie lo sabe, revisaré mi pregunta para preguntar también: ¿Cuál es la mejor manera de lograr el registro cuando una aplicación de Python está haciendo un muchas llamadas al sistema?

Mi aplicación tiene dos modos. En modo interactivo, quiero que todos los resultados vayan a la pantalla, así como a un archivo de registro, incluido el resultado de cualquier llamada al sistema. En el modo demonio, toda la salida va al registro. El modo Daemon funciona muy bien usando os.dup2(). No puedo encontrar una manera de "conectar" todas las salidas a un registro en modo interactivo, sin modificar todas y cada una de las llamadas al sistema.


En otras palabras, quiero la funcionalidad de la línea de comando 'tee' para cualquier salida generada por una aplicación python, incluida la salida de llamada del sistema .

Para aclarar:

Para redirigir toda la salida, hago algo como esto, y funciona muy bien:

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

Lo bueno de esto es que no requiere llamadas de impresión especiales del resto del código. El código también ejecuta algunos comandos de shell, por lo que es bueno no tener que lidiar con cada uno de sus resultados individualmente.

Simplemente, quiero hacer lo mismo, excepto duplicar en lugar de redirigir.

A primera vista, pensé que simplemente invertir los dup2's debería funcionar. ¿Por qué no lo hace? Aquí está mi prueba:

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)

El archivo "a.log" debe ser idéntico al que se muestra en la pantalla.


Si observa la página de manual ( manpagez.com/man/2/dup2 ), el segundo argumento de dup2 siempre está cerrado (si ya está abierto). Por lo tanto, en su "solución rota" se cierra de vez en cuando y luego reasigna sus filenos a sys.stdout.
Jacob Gabrielson

1
Re: su edición: esto no es infrecuente, he hecho algo similar algunas veces (en otros idiomas). Si bien Unix permitirá múltiples "alias" para el mismo identificador de archivo, no "dividirá" un identificador de archivo (cópielo en varios otros). Por lo tanto, debe implementar "tee" usted mismo (o simplemente usar "tee", vea mi respuesta cruda).
Jacob Gabrielson el

Creo que la respuesta de JohnT es mejor que la respuesta real aceptada. Es posible que desee cambiar la respuesta aceptada.
Phong

"Estoy haciendo algo que no es estándar". Realmente lo estás haciendo, la gente simplemente envía sus registros a stderr y trata desde la línea de comandos.
khachik

Respuestas:


55

Como se siente cómodo generando procesos externos a partir de su código, podría usarlo teesolo. No conozco ninguna llamada al sistema Unix que haga exactamente lo que teehace.

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

También puede emular teeusando el paquete de multiprocesamiento (o usar el procesamiento si está usando Python 2.5 o anterior).

Actualizar

Aquí hay una versión compatible con 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
Bueno, esta respuesta funciona, así que la aceptaré. Aún así, me hace sentir sucio.
drue

2
Acabo de publicar una implementación pura de Python de tee (compatible con py2 / 3) que se puede ejecutar en cualquier plataforma y también se puede usar en diferentes configuraciones de registro. stackoverflow.com/questions/616645/…
sorin

8
Si Python se ejecuta en una de mis máquinas y la solución no, entonces esa no es una solución pitónica. Votado a favor debido a eso.
anatoly techtonik

2
Según esta publicación, la línea sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)ya no funciona desde python 3.3 (ver PEP 3116)
Ken Myers

1
Obtuve el error "sys: 1: ResourceWarning: archivo no cerrado <_io.BufferedWriter name = 5>", así que tuve que agregarlo tee.stdin.close()al final de mi programa. También obtengo "ResourceWarning: el subproceso 1842 todavía se está ejecutando", y al agregarlo sys.stdout.close(); sys.stderr.close()al final del programa lo soluciona.
matthieu

136

Tuve este mismo problema antes y encontré este fragmento muy útil:

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

de: http://mail.python.org/pipermail/python-list/2007-May/438106.html


77
+1 para manejar la reasignación sys.stdout internamente para que pueda finalizar el registro eliminando el objeto Tee
Ben Blank

12
Yo agregaría un rubor a eso. Por ejemplo: 'self.file.flush ()'
Luke Stanley

44
No estoy de acuerdo con el módulo de registro. Excelente para algunos violines. El registro es demasiado grande para eso.
Kobor42

44
Asegúrese de anotar la versión revisada en este seguimiento de la discusión vinculada en la respuesta.
Martineau

44
Eso no funcionará. __del__no se llama hasta el final de la ejecución. Ver stackoverflow.com/questions/6104535/…
Nux

77

La printinstrucción llamará al write()método de cualquier objeto que asigne a sys.stdout.

Giraba una pequeña clase para escribir en dos lugares a la vez ...

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

Ahora la printdeclaración hará eco en la pantalla y se agregará a su archivo de registro:

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

Esto es obviamente rápido y sucio. Algunas notas:

  • Probablemente deberías parametizar el nombre del archivo de registro.
  • Probablemente debería revertir sys.stdout <stdout>si no va a iniciar sesión mientras dure el programa.
  • Es posible que desee escribir en varios archivos de registro a la vez, o manejar diferentes niveles de registro, etc.

Todo esto es lo suficientemente sencillo como para que me sienta cómodo dejándolos como ejercicios para el lector. La idea clave aquí es que printsimplemente llama a un "objeto similar a un archivo" al que está asignado sys.stdout.


Exactamente lo que iba a publicar, más o menos. +1 cuando soluciona el problema con la escritura que no tiene un argumento propio. Además, sería mejor diseñar que se pasara el archivo en el que vas a escribir. Demonios, también podría ser mejor diseñar que se pasara stdout.
Devin Jeanpierre 05 de

@Devin, sí, esto fue rápido y sucio, tomaré algunas notas para posibles mejoras tempranas.
Tríptico

77
Seleccioné esta respuesta demasiado pronto. Funciona muy bien para "imprimir", pero no tanto para la salida de comandos externos.
drue

2
La clase Logger también debe definir un método flush () como "def flush (): self.terminal.flush (); self.log.flush ()"
blokeley

55
Usted dice The print statement will call the write() method of any object you assign to sys.stdout. ¿Y qué hay de otras funciones que envían datos a stdout sin usar print? Por ejemplo, si creo un proceso usando subprocess.callsu salida, va a la consola pero no al log.datarchivo ... ¿hay alguna manera de arreglar eso?
jpo38

64

Lo que realmente quieres es un loggingmódulo de la biblioteca estándar. Cree un registrador y adjunte dos controladores, uno estaría escribiendo en un archivo y el otro en stdout o stderr.

Consulte Registro en múltiples destinos para más detalles.


9
El módulo de registro no registra excepciones y otros resultados importantes en stdout, lo que puede ser útil al analizar registros en el servidor de compilación (por ejemplo).
anatoly techtonik

2
loggingel módulo no redirigirá la salida de las llamadas del sistema comoos.write(1, b'stdout')
jfs

17

Aquí hay otra solución, que es más general que las otras: admite la división de salida (escrita en sys.stdout) a cualquier número de objetos similares a archivos. No hay requisito de que __stdout__esté incluido.

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

NOTA: Esta es una prueba de concepto. La implementación aquí no está completa, ya que solo envuelve los métodos de los objetos tipo archivo (p write. Ej. ), Omitiendo miembros / propiedades / setattr, etc. Sin embargo, probablemente sea lo suficientemente bueno para la mayoría de las personas como está actualmente.

Lo que me gusta de ella, aparte de su generalidad, es que es limpia en el sentido de que no tiene ningún llamadas directas a write, flush, os.dup2, etc.


3
Tendría init take * files, no archivos, pero de lo contrario, sí, esto. Ninguna de las otras soluciones aísla la funcionalidad "tee" sin intentar resolver otros problemas. Si desea poner un prefijo en todo lo que genera, puede ajustar esta clase en una clase de escritor de prefijo. (Si desea poner un prefijo en una sola secuencia, se ajusta una secuencia y se la entrega a esta clase). Esta también tiene la ventaja de que multifile ([]) crea un archivo que ignora todo (como open ('/ dev /nulo')).
Ben

¿Por qué tener _wrapaquí en absoluto? ¿No podrías copiar el código allí __getattr__y funciona igual?
Timotree

@Ben realmente multifile([])crea un archivo que genera un archivo UnboundLocalErrorcada vez que llama a uno de sus métodos. ( resse devuelve sin ser asignado)
timotree

13

Como se describe en otra parte, quizás la mejor solución es usar el módulo de registro directamente:

import logging

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

Sin embargo, hay algunas ocasiones (raras) en las que realmente desea redirigir stdout. Tuve esta situación cuando estaba extendiendo el comando runserver de django que usa print: no quería hackear la fuente de django pero necesitaba las declaraciones de print para ir a un archivo.

Esta es una forma de redirigir stdout y stderr fuera del shell utilizando el módulo de registro:

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'

Solo debe usar esta implementación de LogFile si realmente no puede usar el módulo de registro directamente.


11

Escribí una tee()implementación en Python que debería funcionar para la mayoría de los casos, y también funciona en Windows.

https://github.com/pycontribs/tendo

Además, puede usarlo en combinación con el loggingmódulo de Python si lo desea.


Hmm, ese enlace ya no funciona, ¿en algún otro lugar se puede encontrar?
Danny Staple

1
wow, tu paquete es genial, especialmente si sabes lo engorrosa que es la cultura de la consola de Windows pero no te rindes para que funcione.
n611x007

8

(Ah, solo vuelva a leer su pregunta y vea que esto no se aplica).

Aquí hay un programa de muestra que utiliza el módulo de registro de Python . Este módulo de registro ha estado en todas las versiones desde 2.3. En este ejemplo, el registro es configurable por opciones de línea de comando.

En modo silencioso solo se registrará en un archivo, en modo normal se registrará tanto en un archivo como en la consola.

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

Buena respuesta. Vi algunas formas realmente intrincadas de replicar el inicio de sesión en la consola, pero hacer un StreamHandler con stderr fue la respuesta que estaba buscando :)
meatvest

El código es bueno, no responde a la pregunta: esto genera el registro en un archivo y stderr, la pregunta original pedía duplicar el stderr en un archivo de registro.
emem

8

Para completar la respuesta de John T: https://stackoverflow.com/a/616686/395687

Agregué __enter__y __exit__métodos para usarlo como administrador de contexto con la withpalabra clave, que le da a este código

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

Entonces se puede usar como

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

1
Me gustaría mover la __del__funcionalidad a__exit__
vontrapp

1
De hecho, creo que usar __del__es una mala idea. Debe moverse a una función 'cerrar' que se llama __exit__.
cladmi

7

Sé que esta pregunta ha sido respondida repetidamente, pero para esto tomé la respuesta principal de la respuesta de John T y la modifiqué para que contenga el rubor sugerido y seguí su versión revisada vinculada. También he agregado la entrada y la salida como se menciona en la respuesta de cladmi para usar con la declaración with. Además, la documentación menciona el uso de archivos de descarga, os.fsync()así que también he agregado eso. No sé si realmente necesitas eso, pero está ahí.

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

Entonces puedes usarlo

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

o

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

Muchas Thnaks @Status resolviste mi pregunta ( stackoverflow.com/questions/39143417/… ). Pondré un enlace a su solución.
Mohammad ElNesr

1
@MohammadElNesr Acabo de darme cuenta de un problema con el código cuando se usa con una declaración with. Lo arreglé y ahora se cierra correctamente al final de un bloque.
Estado del

1
Esto funcionó muy bien para mí, solo necesitaba cambiar el modo hacia mode="ab"y en la writefunciónself.file.write(message.encode("utf-8"))
ennetws el

4

Otra solución que utiliza el módulo de registro:

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

Ninguna de las respuestas anteriores realmente parece responder al problema planteado. Sé que este es un hilo viejo, pero creo que este problema es mucho más simple de lo que todos lo están resolviendo:

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)

Ahora esto repetirá todo en el controlador sys.stderr normal y su archivo. Crea otra clase tee_outpara sys.stdout.


2
Una respuesta similar y mejor se publicó durante dos años antes de esta: stackoverflow.com/a/616686 . Su método es muy costoso: cada llamada a tee=tee_err();tee.write('');tee.write('');...abrir + cierra un archivo para cada uno write. Consulte stackoverflow.com/q/4867468 y stackoverflow.com/q/164053 para ver argumentos en contra de esta práctica.
Rob W

3

Según una solicitud de @ user5359531 en los comentarios bajo la respuesta de @John T , aquí hay una copia de la publicación referenciada a la versión revisada de la discusión vinculada en esa respuesta:

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

Estoy escribiendo un script para ejecutar scripts de línea cmd. (Porque en algunos casos, simplemente no hay un sustituto viable para un comando de Linux, como el caso de rsync).

Lo que realmente quería era usar el mecanismo de registro de python predeterminado en todos los casos en los que fuera posible, pero aún capturar cualquier error cuando algo salía mal y no se había previsto.

Este código parece hacer el truco. Puede que no sea particularmente elegante o eficiente (aunque no usa string + = string, por lo que al menos no tiene ese cuello de botella potencial particular). Lo estoy publicando en caso de que le dé a alguien otras ideas útiles.

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)

Obviamente, si no está tan sujeto a la fantasía como yo, reemplace LOG_IDENTIFIER con otra cadena que no le gustaría ver a alguien escribir en un registro.


0

Si desea registrar toda la salida en un archivo Y enviarla a un archivo de texto, puede hacer lo siguiente. Es un poco hacky pero funciona:

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!")

EDITAR: tenga en cuenta que esto no registra errores a menos que redirija sys.stderr a sys.stdout

EDIT2: Un segundo problema es que tienes que pasar 1 argumento a diferencia de la función incorporada.

EDITAR3: vea el código anterior para escribir stdin y stdout en la consola y el archivo con stderr solo yendo a archivo

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

Escribí un reemplazo completo para sys.stderry simplemente dupliqué el cambio stderrde nombre del código stdoutpara que también esté disponible para reemplazar sys.stdout.

Para ello se crea el mismo tipo de objeto como la corriente stderry stdout, y remitir a los métodos para el sistema original stderry 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

Para usar esto, solo puede llamar StdErrReplament::lock(logger)y StdOutReplament::lock(logger) pasar el registrador que desea usar para enviar el texto de salida. Por ejemplo:

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

Al ejecutar este código, verá en la pantalla:

ingrese la descripción de la imagen aquí

Y en el contenido del archivo:

ingrese la descripción de la imagen aquí

Si desea ver también el contenido de las log.debugllamadas en la pantalla, deberá agregar un controlador de flujo a su registrador. En este caso sería así:

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

Lo que se generaría así cuando se ejecute:

ingrese la descripción de la imagen aquí

Si bien aún se guardaría esto en el archivo my_log_file.txt:

ingrese la descripción de la imagen aquí

Al deshabilitar esto StdErrReplament:unlock(), solo restaurará el comportamiento estándar de la stderrsecuencia, ya que el registrador adjunto nunca se puede desconectar porque alguien más puede tener una referencia a su versión anterior. Es por eso que es un singleton global que nunca puede morir. Por lo tanto, en caso de volver a cargar este módulo impo algo más, nunca volverá a capturar la corriente sys.stderrcomo ya se inyectó en ella y la guardará internamente.


55
Un sorprendente nivel de complejidad accidental para duplicar una secuencia.
Attila Lendvai
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.