Advertencia

¡Ayúdanos a traducir la documentación oficial de Python al Español! Puedes encontrar más información en Como contribuir. Ayuda a acercar Python a más personas de habla hispana.

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 With Statement Context Managers.

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 Asynchronous Context Managers.

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 tryexceptfinally 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('http://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)

Retorna un administrador de contexto que suprima cualquiera de las excepciones especificadas si se producen en el cuerpo de una instrucción with y luego reanuda la ejecución con la primera instrucción que sigue al final de la instrucción with.

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.

Por ejemplo, la salida de help() normalmente se envía a sys.stdout. Puede capturar esa salida en una cadena redirigiendo la salida a un objeto io.StringIO:

f = io.StringIO()
with redirect_stdout(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

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.

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.

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(Callback, self).__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