"contextlib" --- Utilidades para declaraciones de contexto "with"
*****************************************************************

**Código fuente:** Lib/contextlib.py

======================================================================

Este módulo proporciona utilidades para tareas comunes que involucran
la declaración "with". Para obtener más información, consulte también
Tipos Gestores de Contexto y Gestores de Contexto en la Declaración
with.


Utilidades
==========

Funciones y clases proporcionadas:

class contextlib.AbstractContextManager

   Una *clase base abstracta* para clases que implementan
   "object.__aenter__()" y "object.__exit__()". Se proporciona una
   implementación predeterminada para "object.__enter__()" que retorna
   "self" mientras que "object.__exit__()" es un método abstracto que
   por defecto retorna "None". Véase también la definición de Tipos
   Gestores de Contexto.

   Nuevo en la versión 3.6.

class contextlib.AbstractAsyncContextManager

   Una *clase base abstracta* para clases que implementan
   "object.__aenter__()" y "object.__aexit__()". Se proporciona una
   implementación predeterminada para "object.__aenter__()" que
   retorna "self" mientras que "object.__aexit__()" es un método
   abstracto que por defecto retorna "None". Véase también la
   definición de Gestores de Contexto Asíncronos.

   Nuevo en la versión 3.7.

@contextlib.contextmanager

   Esta función es *decorador* que se puede usar para definir una
   función de fábrica para gestores de contexto de declaración "with",
   sin necesidad de crear una clase o separar "__enter__()" y
   "__exit__()" métodos.

   Si bien muchos objetos admiten de forma nativa el uso con
   declaraciones, a veces es necesario administrar un recurso que no
   sea un administrador de contexto por sí mismo y no implemente un
   método "close()" para usar con "contextlib.close"

   Un ejemplo abstracto sería el siguiente para garantizar la gestión
   correcta de los recursos:

      from contextlib import contextmanager

      @contextmanager
      def managed_resource(*args, **kwds):
          # Code to acquire resource, e.g.:
          resource = acquire_resource(*args, **kwds)
          try:
              yield resource
          finally:
              # Code to release resource, e.g.:
              release_resource(resource)

      >>> with managed_resource(timeout=3600) as resource:
      ...     # Resource is released at the end of this block,
      ...     # even if code in the block raises an exception

   La función que se está decorando debe retornar un iterador
   *generador* cuando se llama. Este iterador debe producir
   exactamente un valor, que estará vinculado a los objetivos en la
   "with" declaración de la cláusula "as", si existe.

   En el punto donde el generador cede, se ejecuta el bloque anidado
   en la palabra clave "with". El generador se reanuda luego de salir
   del bloque. Si se produce una excepción no controlada en el bloque,
   se vuelve a plantear dentro del generador en el punto donde se
   produjo el rendimiento. Por lo tanto, puede usar una declaración
   "try"..."except"..."finally" para atrapar el error (si lo hay), o
   asegurarse de que se realice una limpieza. Si una excepción queda
   atrapada simplemente para registrarla o realizar alguna acción (en
   lugar de suprimirla por completo), el generador debe volver a
   generar esa excepción. De lo contrario, el administrador de
   contexto del generador indicará a la palabra clave "with" que se ha
   manejado la excepción, y la ejecución se reanudará con la
   declaración inmediatamente siguiente a la palabra clave "with".

   "contextmanager()" usa "ContextDecorator" para que los gestores de
   contexto que crea se puedan usar como decoradores, así como en
   declaraciones "with". Cuando se usa como decorador, se crea
   implícitamente una nueva instancia de generador en cada llamada de
   función (esto permite que los gestores de contexto "de-un-tiro"
   creados por "contextmanager()" cumplan el requisito de que los
   gestores de contexto admitan múltiples invocaciones para ser
   utilizado como decoradores).

   Distinto en la versión 3.2: Uso de "ContextDecorator".

@contextlib.asynccontextmanager

   Similar a "contextmanager()", pero crea un administrador de
   contexto asíncrono.

   Esta función es *decorador* que se puede utilizar para definir una
   función de fábrica para gestores de contexto asíncrono de
   declaración "async with", sin necesidad de crear una clase o
   separar "__aenter__()" y métodos "__aexit__()". Debe aplicarse a
   una función *asynchronous generator*.

   Un ejemplo simple:

      from contextlib import asynccontextmanager

      @asynccontextmanager
      async def get_connection():
          conn = await acquire_db_connection()
          try:
              yield conn
          finally:
              await release_db_connection(conn)

      async def get_all_users():
          async with get_connection() as conn:
              return conn.query('SELECT ...')

   Nuevo en la versión 3.7.

contextlib.closing(thing)

   retorna un gestor de contexto que cierra *thing* al completar el
   bloque. Esto es básicamente equivalente a:

      from contextlib import contextmanager

      @contextmanager
      def closing(thing):
          try:
              yield thing
          finally:
              thing.close()

   Y te permite escribir código como este:

      from contextlib import closing
      from urllib.request import urlopen

      with closing(urlopen('https://www.python.org')) as page:
          for line in page:
              print(line)

   sin necesidad de cerrar explícitamente la "page". Incluso si se
   produce un error, se llamará a "page.close()" cuando salga el
   bloque "with".

contextlib.nullcontext(enter_result=None)

   retorna un gestor de contexto que retorna *enter_result* de
   "__enter__", pero de lo contrario no hace nada. Está destinado a
   ser utilizado como un sustituto para un administrador de contexto
   opcional, por ejemplo:

      def myfunction(arg, ignore_exceptions=False):
          if ignore_exceptions:
              # Use suppress to ignore all exceptions.
              cm = contextlib.suppress(Exception)
          else:
              # Do not ignore any exceptions, cm has no effect.
              cm = contextlib.nullcontext()
          with cm:
              # Do something

   Un ejemplo usando *enter_result*:

      def process_file(file_or_path):
          if isinstance(file_or_path, str):
              # If string, open file
              cm = open(file_or_path)
          else:
              # Caller is responsible for closing file
              cm = nullcontext(file_or_path)

          with cm as file:
              # Perform processing on the file

   Nuevo en la versión 3.7.

contextlib.suppress(*exceptions)

   Return a context manager that suppresses any of the specified
   exceptions if they occur in the body of a "with" statement and then
   resumes execution with the first statement following the end of the
   "with" statement.

   Al igual que con cualquier otro mecanismo que suprima completamente
   las excepciones, este administrador de contexto debe usarse solo
   para cubrir errores muy específicos en los que se sabe que
   continuar silenciosamente con la ejecución del programa es lo
   correcto.

   Por ejemplo:

      from contextlib import suppress

      with suppress(FileNotFoundError):
          os.remove('somefile.tmp')

      with suppress(FileNotFoundError):
          os.remove('someotherfile.tmp')

   Este código es equivalente a:

      try:
          os.remove('somefile.tmp')
      except FileNotFoundError:
          pass

      try:
          os.remove('someotherfile.tmp')
      except FileNotFoundError:
          pass

   Este gestor de contexto es reentrant.

   Nuevo en la versión 3.4.

contextlib.redirect_stdout(new_target)

   Administrador de contexto para redirigir temporalmente "sys.stdout"
   a otro archivo u objeto similar a un archivo.

   Esta herramienta agrega flexibilidad a las funciones o clases
   existentes cuya salida está programada para stdout.

   For example, the output of "help()" normally is sent to
   *sys.stdout*. You can capture that output in a string by
   redirecting the output to an "io.StringIO" object. The replacement
   stream is returned from the "__enter__" method and so is available
   as the target of the "with" statement:

      with redirect_stdout(io.StringIO()) as f:
          help(pow)
      s = f.getvalue()

   Para enviar la salida de "help()" a un archivo en el disco,
   redirija la salida a un archivo normal:

      with open('help.txt', 'w') as f:
          with redirect_stdout(f):
              help(pow)

   Para enviar la salida de "help()" a *sys.stderr*:

      with redirect_stdout(sys.stderr):
          help(pow)

   Tenga en cuenta que el efecto secundario global en "sys.stdout"
   significa que este administrador de contexto no es adecuado para su
   uso en el código de la biblioteca y en la mayoría de las
   aplicaciones con subprocesos. Tampoco tiene efecto en la salida de
   subprocesos. Sin embargo, sigue siendo un enfoque útil para muchos
   scripts de utilidad.

   Este gestor de contexto es reentrant.

   Nuevo en la versión 3.4.

contextlib.redirect_stderr(new_target)

   Similar a "redirect_stdout()" pero redirigiendo "sys.stderr" a otro
   archivo u objeto similar a un archivo.

   Este gestor de contexto es reentrant.

   Nuevo en la versión 3.5.

class contextlib.ContextDecorator

   Una clase base que permite que un administrador de contexto también
   se use como decorador.

   Los gestores de contexto que heredan de "ContextDecorator" tienen
   que implementar "__enter__" y "__exit__" de manera normal.
   "__exit__" conserva su manejo opcional de excepciones incluso
   cuando se usa como decorador.

   "ContextDecorator" es utilizado por "contextmanager()", por lo que
   obtiene esta funcionalidad automáticamente.

   Ejemplo de "ContextDecorator":

      from contextlib import ContextDecorator

      class mycontext(ContextDecorator):
          def __enter__(self):
              print('Starting')
              return self

          def __exit__(self, *exc):
              print('Finishing')
              return False

      >>> @mycontext()
      ... def function():
      ...     print('The bit in the middle')
      ...
      >>> function()
      Starting
      The bit in the middle
      Finishing

      >>> with mycontext():
      ...     print('The bit in the middle')
      ...
      Starting
      The bit in the middle
      Finishing

   Este cambio es solo azúcar sintáctico para cualquier construcción
   de la siguiente forma:

      def f():
          with cm():
              # Do stuff

   "ContextDecorator" le permite escribir en su lugar:

      @cm()
      def f():
          # Do stuff

   Deja en claro que el "cm" se aplica a toda la función, en lugar de
   solo una parte de ella (y guardar un nivel de sangría también es
   bueno).

   Los gestores de contexto existentes que ya tienen una clase base
   pueden ampliarse utilizando "ContextDecorator" como una clase
   mezcla (*mixin*):

      from contextlib import ContextDecorator

      class mycontext(ContextBaseClass, ContextDecorator):
          def __enter__(self):
              return self

          def __exit__(self, *exc):
              return False

   Nota:

     Como la función decorada debe poder llamarse varias veces, el
     gestor de contexto subyacente debe admitir el uso en múltiples
     declaraciones "with". Si este no es el caso, se debe utilizar la
     construcción original con la declaración explícita "with" dentro
     de la función.

   Nuevo en la versión 3.2.

class contextlib.ExitStack

   Un gestor de contexto que está diseñado para facilitar la
   combinación programática de otros gestores de contexto y funciones
   de limpieza, especialmente aquellas que son opcionales o que de
   otro modo son impulsadas por los datos de entrada.

   Por ejemplo, un conjunto de archivos puede manejarse fácilmente en
   una sola declaración de la siguiente manera:

      with ExitStack() as stack:
          files = [stack.enter_context(open(fname)) for fname in filenames]
          # All opened files will automatically be closed at the end of
          # the with statement, even if attempts to open files later
          # in the list raise an exception

   The "__enter__()" method returns the "ExitStack" instance, and
   performs no additional operations.

   Cada instancia mantiene una pila de retrollamadas registradas que
   se llaman en orden inverso cuando la instancia se cierra (ya sea
   explícita o implícitamente al final de una instrucción "with").
   Tenga en cuenta que las retrollamadas *no* se invocan
   implícitamente cuando la instancia de la pila de contexto se
   recolecta basura.

   Este modelo de pila se utiliza para que los administradores de
   contexto que adquieren sus recursos en su método "__init__" (como
   los objetos de archivo) se puedan manejar correctamente.

   Dado que las retrollamadas registradas se invocan en el orden
   inverso del registro, esto termina comportándose como si se
   hubieran utilizado múltiples instrucciones anidadas "with" con el
   conjunto registrado de retrollamadas. Esto incluso se extiende al
   manejo de excepciones: si una retrollamada interna suprime o
   reemplaza una excepción, las retrollamadas externas se pasarán
   argumentos basados en ese estado actualizado.

   Esta es una API de nivel relativamente bajo que se ocupa de los
   detalles de desenrollar correctamente la pila de retrollamadas de
   salida. Proporciona una base adecuada para administradores de
   contexto de nivel superior que manipulan la pila de salida en
   formas específicas de la aplicación.

   Nuevo en la versión 3.3.

   enter_context(cm)

      Ingresa a un nuevo administrador de contexto y agrega su método
      "__exit__()" a la pila de retrollamada. El valor de retorno es
      el resultado del método propio del administrador de contexto
      "__enter__()".

      Estos administradores de contexto pueden suprimir excepciones
      tal como lo harían normalmente si se usaran directamente como
      parte de una declaración "with".

   push(exit)

      Agrega un método de gestor de contexto "__exit__()" a la pila de
      retrollamada.

      Como "__enter__" *no* se invoca, este método se puede usar para
      cubrir parte de una implementación "__enter__()" con un método
      propio del gestor de contexto "__exit__()".

      Si se pasa un objeto que no es un administrador de contexto,
      este método supone que es una retrollamada con la misma firma
      que el método "__exit__()" de un gestor de contexto y lo agrega
      directamente a la pila de retrollamada.

      Al retornar valores verdaderos, estas retrollamadas pueden
      suprimir excepciones de la misma manera que el gestor de
      contexto los métodos "__exit__()" pueden hacerlo.

      El objeto pasado se retorna desde la función, lo que permite que
      este método se use como decorador de funciones.

   callback(callback, /, *args, **kwds)

      Acepta una función de retrollamada arbitraria y argumentos y la
      agrega a la pila de retrollamada.

      A diferencia de los otros métodos, las retrollamadas agregadas
      de esta manera no pueden suprimir excepciones (ya que nunca se
      pasan los detalles de excepción).

      La retrollamada pasada se retorna desde la función, lo que
      permite que este método se use como decorador de funciones.

   pop_all()

      Transfiere la pila de retrollamada a una instancia fresca
      "ExitStack" y la retorna. Esta operación no invoca
      retrollamadas; en cambio, ahora se invocarán cuando se cierre la
      nueva pila (ya sea explícita o implícitamente al final de una
      instrucción "with").

      Por ejemplo, un grupo de archivos se puede abrir como una
      operación de "todo o nada" de la siguiente manera:

         with ExitStack() as stack:
             files = [stack.enter_context(open(fname)) for fname in filenames]
             # Hold onto the close method, but don't call it yet.
             close_files = stack.pop_all().close
             # If opening any file fails, all previously opened files will be
             # closed automatically. If all files are opened successfully,
             # they will remain open even after the with statement ends.
             # close_files() can then be invoked explicitly to close them all.

   close()

      Inmediatamente desenrolla la pila de retrollamada, invocando
      retrollamadas en el orden inverso de registro. Para los
      administradores de contexto y las retrollamadas de salida
      registradas, los argumentos pasados indicarán que no se produjo
      ninguna excepción.

class contextlib.AsyncExitStack

   Un gestor de contexto asíncrono, similar a "ExitStack", que admite
   la combinación de gestores de contexto síncrono y asíncrono, además
   de tener rutinas para la lógica de limpieza.

   El método "close()" no está implementado, "aclose()" debe usarse en
   su lugar.

   coroutine enter_async_context(cm)

      Similar a "enter_context()" pero espera un administrador de
      contexto asíncrono.

   push_async_exit(exit)

      Similar a "push()" pero espera un gestor de contexto asíncrono o
      una función de rutina.

   push_async_callback(callback, /, *args, **kwds)

      Similar a "callback()" pero espera una función de rutina.

   coroutine aclose()

      Similar a "close()" pero maneja adecuadamente los objetos de
      espera.

   Continuando con el ejemplo para "asynccontextmanager()":

      async with AsyncExitStack() as stack:
          connections = [await stack.enter_async_context(get_connection())
              for i in range(5)]
          # All opened connections will automatically be released at the end of
          # the async with statement, even if attempts to open a connection
          # later in the list raise an exception.

   Nuevo en la versión 3.7.


Ejemplos y recetas
==================

Esta sección describe algunos ejemplos y recetas para hacer un uso
efectivo de las herramientas proporcionadas por "contextlib".


Apoyando un número variable de gestores de contexto
---------------------------------------------------

El caso de uso principal para "ExitStack" es el que se proporciona en
la documentación de la clase: admite un número variable de gestores de
contexto y otras operaciones de limpieza en una sola "with". La
variabilidad puede provenir de la cantidad de gestores de contexto que
necesitan ser impulsados por la entrada del usuario (como abrir una
colección de archivos especificada por el usuario), o de que algunos
de los gestores de contexto sean opcionales:

   with ExitStack() as stack:
       for resource in resources:
           stack.enter_context(resource)
       if need_special_resource():
           special = acquire_special_resource()
           stack.callback(release_special_resource, special)
       # Perform operations that use the acquired resources

Como se muestra, "ExitStack" también hace que sea bastante fácil de
usar "with" para administrar recursos arbitrarios que no admiten de
forma nativa el protocolo de gestión de contexto.


Capturando excepciones de los métodos "__enter__"
-------------------------------------------------

Ocasionalmente es deseable capturar excepciones de una implementación
del método "__enter__", *sin* capturar inadvertidamente excepciones
del cuerpo de la declaración "with" o el método "__exit__" del gestor
de contexto. Al usar "ExitStack", los pasos en el protocolo de gestor
de contexto se pueden separar ligeramente para permitir esto:

   stack = ExitStack()
   try:
       x = stack.enter_context(cm)
   except Exception:
       # handle __enter__ exception
   else:
       with stack:
           # Handle normal case

Es probable que la necesidad de hacer esto indique que la API
subyacente debería proporcionar una interfaz de administración de
recursos directa para usar con "try"/"except"/"finally", pero no todas
las API están bien diseñados en ese sentido. Cuando un administrador
de contexto es la única API de administración de recursos
proporcionada, entonces "ExitStack" puede facilitar el manejo de
diversas situaciones que no se pueden manejar directamente en una
declaración "with".


Limpieza en una implementación "__enter__"
------------------------------------------

Como se señala en la documentación de "ExitStack.push()", este método
puede ser útil para limpiar un recurso ya asignado si fallan los pasos
posteriores en "__enter__()".

Aquí hay un ejemplo de cómo hacer esto para un administrador de
contexto que acepta funciones de adquisición y liberación de recursos,
junto con una función de validación opcional, y las asigna al
protocolo de administración de contexto:

   from contextlib import contextmanager, AbstractContextManager, ExitStack

   class ResourceManager(AbstractContextManager):

       def __init__(self, acquire_resource, release_resource, check_resource_ok=None):
           self.acquire_resource = acquire_resource
           self.release_resource = release_resource
           if check_resource_ok is None:
               def check_resource_ok(resource):
                   return True
           self.check_resource_ok = check_resource_ok

       @contextmanager
       def _cleanup_on_error(self):
           with ExitStack() as stack:
               stack.push(self)
               yield
               # The validation check passed and didn't raise an exception
               # Accordingly, we want to keep the resource, and pass it
               # back to our caller
               stack.pop_all()

       def __enter__(self):
           resource = self.acquire_resource()
           with self._cleanup_on_error():
               if not self.check_resource_ok(resource):
                   msg = "Failed validation for {!r}"
                   raise RuntimeError(msg.format(resource))
           return resource

       def __exit__(self, *exc_details):
           # We don't need to duplicate any of our resource release logic
           self.release_resource()


Reemplazar cualquier uso de "try-finally" y marcar variables
------------------------------------------------------------

Un patrón que a veces verá es una declaración de "try-finally" con una
variable de indicador para indicar si el cuerpo de la cláusula
"finally" debe ejecutarse o no. En su forma más simple (que ya no
puede manejarse simplemente usando una cláusula "except" en su lugar),
se parece a esto:

   cleanup_needed = True
   try:
       result = perform_operation()
       if result:
           cleanup_needed = False
   finally:
       if cleanup_needed:
           cleanup_resources()

Al igual que con cualquier código basado en la declaración "try", esto
puede causar problemas de desarrollo y revisión, porque el código de
configuración y el código de limpieza pueden terminar separados por
secciones de código arbitrariamente largas.

"ExitStack" hace posible registrar una retrollamada para su ejecución
al final de una instrucción "with", y luego decide omitir la ejecución
de esa retrollamada:

   from contextlib import ExitStack

   with ExitStack() as stack:
       stack.callback(cleanup_resources)
       result = perform_operation()
       if result:
           stack.pop_all()

Esto permite que el comportamiento de limpieza previsto se haga
explícito por adelantado, en lugar de requerir una variable de
indicador separada.

Si una aplicación particular usa mucho este patrón, puede
simplificarse aún más por medio de una pequeña clase auxiliar:

   from contextlib import ExitStack

   class Callback(ExitStack):
       def __init__(self, callback, /, *args, **kwds):
           super().__init__()
           self.callback(callback, *args, **kwds)

       def cancel(self):
           self.pop_all()

   with Callback(cleanup_resources) as cb:
       result = perform_operation()
       if result:
           cb.cancel()

Si la limpieza del recurso no está bien agrupada en una función
independiente, entonces todavía es posible usar la forma decoradora de
"ExitStack.callback()" para declarar la limpieza del recurso por
adelantado:

   from contextlib import ExitStack

   with ExitStack() as stack:
       @stack.callback
       def cleanup_resources():
           ...
       result = perform_operation()
       if result:
           stack.pop_all()

Debido a la forma en que funciona el protocolo decorador, una función
de retrollamada declarada de esta manera no puede tomar ningún
parámetro. En cambio, se debe acceder a los recursos que se liberarán
como variables de cierre.


Usar un gestor de contexto como decorador de funciones
------------------------------------------------------

"ContextDecorator" hace posible usar un gestor de contexto tanto en
una instrucción ordinaria "with" como también como decorador de
funciones.

Por ejemplo, a veces es útil envolver funciones o grupos de
declaraciones con un registrador que puede rastrear la hora de entrada
y la hora de salida. En lugar de escribir tanto un decorador de
funciones como un administrador de contexto para la tarea, heredar de
"ContextDecorator" proporciona ambas capacidades en una sola
definición:

   from contextlib import ContextDecorator
   import logging

   logging.basicConfig(level=logging.INFO)

   class track_entry_and_exit(ContextDecorator):
       def __init__(self, name):
           self.name = name

       def __enter__(self):
           logging.info('Entering: %s', self.name)

       def __exit__(self, exc_type, exc, exc_tb):
           logging.info('Exiting: %s', self.name)

Las instancias de esta clase se pueden usar como un gestor de
contexto:

   with track_entry_and_exit('widget loader'):
       print('Some time consuming activity goes here')
       load_widget()

Y también como decorador de funciones:

   @track_entry_and_exit('widget loader')
   def activity():
       print('Some time consuming activity goes here')
       load_widget()

Tenga en cuenta que hay una limitación adicional cuando se usan
administradores de contexto como decoradores de funciones: no hay
forma de acceder al valor de retorno de "__enter__()". Si se necesita
ese valor, aún es necesario usar una declaración explícita "with".

Ver también:

  **PEP 343** - La declaración "with"
     La especificación, antecedentes y ejemplos de la declaración de
     Python "with".


Gestores de contexto de uso único, reutilizables y reentrantes
==============================================================

La mayoría de los gestores de contexto están escritos de una manera
que significa que solo se pueden usar de manera efectiva en una
declaración "with" una vez. Estos administradores de contexto de un
solo uso deben crearse de nuevo cada vez que se usan; si intenta
usarlos por segunda vez, se activará una excepción o, de lo contrario,
no funcionará correctamente.

Esta limitación común significa que generalmente es aconsejable crear
gestores de contexto directamente en el encabezado de la palabra clave
"with" donde se usan (como se muestra en todos los ejemplos de uso
anteriores).

Los archivos son un ejemplo de gestores de contexto de un solo uso, ya
que la primera "with" cerrará el archivo, evitando cualquier otra
operación de E/S que use ese objeto de archivo.

Los gestores de contexto creados usando "contextmanager()" también son
gestores de contexto de un solo uso, y se quejarán de la falla del
generador subyacente si se intenta usarlos por segunda vez:

   >>> from contextlib import contextmanager
   >>> @contextmanager
   ... def singleuse():
   ...     print("Before")
   ...     yield
   ...     print("After")
   ...
   >>> cm = singleuse()
   >>> with cm:
   ...     pass
   ...
   Before
   After
   >>> with cm:
   ...     pass
   ...
   Traceback (most recent call last):
       ...
   RuntimeError: generator didn't yield


Gestores contextuales reentrantes
---------------------------------

Los gestores de contexto más sofisticados pueden ser "reentrantes".
Estos administradores de contexto no solo se pueden usar en múltiples
declaraciones "with", sino que también se pueden usar *inside* a
"with" que ya está usando el mismo gestor de contexto.

"threading.RLock" es un ejemplo de un administrador de contexto
reentrante, como son "suppress()" y "redirect_stdout()". Aquí hay un
ejemplo muy simple de uso reentrante:

   >>> from contextlib import redirect_stdout
   >>> from io import StringIO
   >>> stream = StringIO()
   >>> write_to_stream = redirect_stdout(stream)
   >>> with write_to_stream:
   ...     print("This is written to the stream rather than stdout")
   ...     with write_to_stream:
   ...         print("This is also written to the stream")
   ...
   >>> print("This is written directly to stdout")
   This is written directly to stdout
   >>> print(stream.getvalue())
   This is written to the stream rather than stdout
   This is also written to the stream

Es más probable que los ejemplos del mundo real de reentrada impliquen
múltiples funciones que se llaman entre sí y, por lo tanto, sean mucho
más complicadas que este ejemplo.

Tenga en cuenta también que ser reentrante *no* es lo mismo que ser
seguro para subprocesos. "redirect_stdout()", por ejemplo,
definitivamente no es seguro para subprocesos, ya que realiza una
modificación global al estado del sistema al vincular "sys.stdout" a
una secuencia diferente.


Gestores contextuales reutilizables
-----------------------------------

Distintos de los administradores de contexto de uso único y reentrante
son los administradores de contexto "reutilizables" (o, para ser
completamente explícitos, los administradores de contexto
"reutilizables, pero no reentrantes", ya que los administradores de
contexto reentrantes también son reutilizables). Estos administradores
de contexto admiten que se usen varias veces, pero fallarán (o de lo
contrario no funcionarán correctamente) si la instancia específica del
administrador de contexto ya se ha utilizado en una declaración que
contiene.

"threading.Lock" es un ejemplo de un gestor de contexto reutilizable,
pero no reentrante (para un bloqueo reentrante, es necesario usar
"threading.RLock" en su lugar).

Otro ejemplo de un administrador de contexto reutilizable, pero no
reentrante es "ExitStack", ya que invoca *all* las retrollamadas
registradas actualmente al dejar cualquier con declaración,
independientemente de dónde se agregaron esas retrollamadas:

   >>> from contextlib import ExitStack
   >>> stack = ExitStack()
   >>> with stack:
   ...     stack.callback(print, "Callback: from first context")
   ...     print("Leaving first context")
   ...
   Leaving first context
   Callback: from first context
   >>> with stack:
   ...     stack.callback(print, "Callback: from second context")
   ...     print("Leaving second context")
   ...
   Leaving second context
   Callback: from second context
   >>> with stack:
   ...     stack.callback(print, "Callback: from outer context")
   ...     with stack:
   ...         stack.callback(print, "Callback: from inner context")
   ...         print("Leaving inner context")
   ...     print("Leaving outer context")
   ...
   Leaving inner context
   Callback: from inner context
   Callback: from outer context
   Leaving outer context

Como muestra el resultado del ejemplo, la reutilización de un solo
objeto de pila en múltiples con declaraciones funciona correctamente,
pero intentar anidarlos hará que la pila se borre al final de la
declaración más interna, lo que es poco probable que sea un
comportamiento deseable.

El uso de instancias separadas "ExitStack" en lugar de reutilizar una
sola instancia evita ese problema:

   >>> from contextlib import ExitStack
   >>> with ExitStack() as outer_stack:
   ...     outer_stack.callback(print, "Callback: from outer context")
   ...     with ExitStack() as inner_stack:
   ...         inner_stack.callback(print, "Callback: from inner context")
   ...         print("Leaving inner context")
   ...     print("Leaving outer context")
   ...
   Leaving inner context
   Callback: from inner context
   Leaving outer context
   Callback: from outer context
