contextlib — Utilitaires pour les contextes s'appuyant sur l'instruction with

Code source : Lib/contextlib.py


Ce module fournit des utilitaires pour les tâches impliquant le mot-clé with. Pour plus d'informations voir aussi Le type gestionnaire de contexte et Gestionnaire de contexte With.

Utilitaires

Fonctions et classes fournies :

class contextlib.AbstractContextManager

Classe mère abstraite pour les classes qui implémentent les méthodes object.__enter__() et object.__exit__(). Une implémentation par défaut de object.__enter__() est fournie, qui renvoie self, et object.__exit__() est une méthode abstraite qui renvoie None par défaut. Voir aussi la définition de Le type gestionnaire de contexte.

Ajouté dans la version 3.6.

class contextlib.AbstractAsyncContextManager

Classe mère abstraite pour les classes qui implémentent les méthodes object.__aenter__() et object.__aexit__(). Une implémentation par défaut de object.__aenter__() est fournie, qui renvoie self, et object.__aexit__() est une méthode abstraite qui renvoie None par défaut. Voir aussi la définition de Gestionnaires de contexte asynchrones.

Ajouté dans la version 3.7.

@contextlib.contextmanager

This function is a decorator that can be used to define a factory function for with statement context managers, without needing to create a class or separate __enter__() and __exit__() methods.

Alors que de nombreux objets s'utilisent nativement dans des blocs with, on trouve parfois des ressources qui nécessitent d'être gérées mais ne sont pas des gestionnaires de contextes, et qui n'implémentent pas de méthode close() pour pouvoir être utilisées avec contextlib.closing

L'exemple abstrait suivant présente comment assurer une gestion correcte des ressources :

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)

The function can then be used like this:

>>> 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 fonction à décorer doit renvoyer un générateur-itérateur quand elle est appelée. Ce générateur ne doit produire qu'une seule valeur, qui est récupérée dans le bloc with à l'aide de la clause as si précisée.

Au moment où le générateur produit une valeur, le bloc imbriqué sous l'instruction with est exécuté. Le générateur est ensuite repris après la sortie du bloc. Si une exception non gérée survient dans le bloc, elle est relayée dans le générateur au niveau de l'instruction yield. Ainsi, vous pouvez utiliser les instructions tryexceptfinally pour attraper l'erreur (s'il y a), ou vous assurer qu'un nettoyage a bien lieu. Si une exception est attrapée dans l'unique but d'être journalisée ou d'effectuer une action particulière (autre que supprimer entièrement l'exception), le générateur se doit de la relayer. Autrement le générateur gestionnaire de contexte doit indiquer à l'instruction with que l'exception a été gérée, et l'exécution reprend sur l'instruction qui suit directement le bloc with.

Le décorateur contextmanager() utilise la classe ContextDecorator afin que les gestionnaires de contexte qu'il crée puissent être utilisés aussi bien en tant que décorateurs qu'avec des instructions with. Quand vous l'utilisez comme décorateur, une nouvelle instance du générateur est créée à chaque appel de la fonction (cela permet aux gestionnaires de contexte à usage unique créés par contextmanager() de remplir la condition de pouvoir être invoqués plusieurs fois afin d'être utilisés comme décorateurs).

Modifié dans la version 3.2: Utilisation de la classe ContextDecorator.

@contextlib.asynccontextmanager

Similaire à contextmanager(), mais crée un gestionnaire de contexte asynchrone.

This function is a decorator that can be used to define a factory function for async with statement asynchronous context managers, without needing to create a class or separate __aenter__() and __aexit__() methods. It must be applied to an asynchronous generator function.

Un exemple 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 ...')

Ajouté dans la version 3.7.

Les gestionnaires de contexte définis avec asynccontextmanager() peuvent s'utiliser comme décorateurs ou dans les instructions async with :

import time
from contextlib import asynccontextmanager

@asynccontextmanager
async def timeit():
    now = time.monotonic()
    try:
        yield
    finally:
        print(f'it took {time.monotonic() - now}s to run')

@timeit()
async def main():
    # ... async code ...

When used as a decorator, a new generator instance is implicitly created on each function call. This allows the otherwise "one-shot" context managers created by asynccontextmanager() to meet the requirement that context managers support multiple invocations in order to be used as decorators.

Modifié dans la version 3.10: Async context managers created with asynccontextmanager() can be used as decorators.

contextlib.closing(thing)

Renvoie un gestionnaire de contexte qui ferme thing à la fin du bloc. C'est essentiellement équivalent à :

from contextlib import contextmanager

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

Et cela vous permet d'écrire du code tel que :

from contextlib import closing
from urllib.request import urlopen

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

sans besoin de fermer explicitement page. Même si une erreur survient, page.close() est appelée à la fermeture du bloc with.

Note

Most types managing resources support the context manager protocol, which closes thing on leaving the with statement. As such, closing() is most useful for third party types that don't support context managers. This example is purely for illustration purposes, as urlopen() would normally be used in a context manager.

contextlib.aclosing(thing)

Return an async context manager that calls the aclose() method of thing upon completion of the block. This is basically equivalent to:

from contextlib import asynccontextmanager

@asynccontextmanager
async def aclosing(thing):
    try:
        yield thing
    finally:
        await thing.aclose()

Significantly, aclosing() supports deterministic cleanup of async generators when they happen to exit early by break or an exception. For example:

from contextlib import aclosing

async with aclosing(my_generator()) as values:
    async for value in values:
        if value == 42:
            break

This pattern ensures that the generator's async exit code is executed in the same context as its iterations (so that exceptions and context variables work as expected, and the exit code isn't run after the lifetime of some task it depends on).

Ajouté dans la version 3.10.

contextlib.nullcontext(enter_result=None)

Renvoie un gestionnaire de contexte dont la méthode __enter__ renvoie enter_result, mais ne fait rien d'autre. L'idée est de l'utiliser comme remplaçant pour un gestionnaire de contexte optionnel, par exemple :

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 exemple utilisant 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

It can also be used as a stand-in for asynchronous context managers:

async def send_http(session=None):
    if not session:
        # If no http session, create it with aiohttp
        cm = aiohttp.ClientSession()
    else:
        # Caller is responsible for closing the session
        cm = nullcontext(session)

    async with cm as session:
        # Send http requests with session

Ajouté dans la version 3.7.

Modifié dans la version 3.10: asynchronous context manager support was added.

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.

Comme pour tous les mécanismes qui suppriment complètement les exceptions, ce gestionnaire de contexte doit seulement être utilisé pour couvrir des cas très spécifiques d'erreurs où il est certain que continuer silencieusement l'exécution du programme est la bonne chose à faire.

Par exemple :

from contextlib import suppress

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

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

Ce code est équivalent à :

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

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

Ce gestionnaire de contexte est réentrant.

If the code within the with block raises a BaseExceptionGroup, suppressed exceptions are removed from the group. Any exceptions of the group which are not suppressed are re-raised in a new group which is created using the original group's derive() method.

Ajouté dans la version 3.4.

Modifié dans la version 3.12: suppress now supports suppressing exceptions raised as part of a BaseExceptionGroup.

contextlib.redirect_stdout(new_target)

Gestionnaire de contexte servant à rediriger temporairement sys.stdout vers un autre fichier ou objet fichier-compatible.

Cet outil ajoute une certaine flexibilité aux fonctions ou classes existantes dont la sortie est envoyée vers la sortie standard.

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

Pour envoyer la sortie de help() vers un fichier sur le disque, redirigez-la sur un fichier normal :

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

Pour envoyer la sortie de help() sur sys.stderr :

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

Notez que l'effet de bord global sur sys.stdout signifie que ce gestionnaire de contexte n'est pas adapté à une utilisation dans le code d'une bibliothèque ni dans la plupart des applications à plusieurs fils d'exécution. Aussi, cela n'a pas d'effet sur la sortie des sous-processus. Cependant, cela reste une approche utile pour beaucoup de scripts utilitaires.

Ce gestionnaire de contexte est réentrant.

Ajouté dans la version 3.4.

contextlib.redirect_stderr(new_target)

Similaire à redirect_stdout() mais redirige sys.stderr vers un autre fichier ou objet fichier-compatible.

Ce gestionnaire de contexte est réentrant.

Ajouté dans la version 3.5.

contextlib.chdir(path)

Non parallel-safe context manager to change the current working directory. As this changes a global state, the working directory, it is not suitable for use in most threaded or async contexts. It is also not suitable for most non-linear code execution, like generators, where the program execution is temporarily relinquished -- unless explicitly desired, you should not yield when this context manager is active.

This is a simple wrapper around chdir(), it changes the current working directory upon entering and restores the old one on exit.

Ce gestionnaire de contexte est réentrant.

Ajouté dans la version 3.11.

class contextlib.ContextDecorator

Une classe mère qui permet à un gestionnaire de contexte d'être aussi utilisé comme décorateur.

Les gestionnaires de contexte héritant de ContextDecorator doivent implémenter __enter__ et __exit__ comme habituellement. __exit__ conserve sa gestion optionnelle des exceptions même lors de l'utilisation en décorateur.

ContextDecorator est utilisé par contextmanager(), donc vous bénéficiez automatiquement de cette fonctionnalité.

Exemple de ContextDecorator :

from contextlib import ContextDecorator

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

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

The class can then be used like this:

>>> @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

Ce changement est simplement un sucre syntaxique pour les constructions de la forme suivante :

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

ContextDecorator vous permet d'écrire à la place :

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

Cela éclaircit le fait que cm s'applique à la fonction entière, et pas seulement à un morceau en particulier (et gagner un niveau d'indentation est toujours appréciable).

Les gestionnaires de contexte existants qui ont déjà une classe mère peuvent être étendus en utilisant ContextDecorator comme une mixin :

from contextlib import ContextDecorator

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

    def __exit__(self, *exc):
        return False

Note

Comme la fonction décorée doit être capable d'être appelée plusieurs fois, le gestionnaire de contexte sous-jacent doit permettre d'être utilisé dans de multiples instructions with. Si ce n'est pas le cas, alors la construction d'origine avec de multiples instructions with au sein de la fonction doit être utilisée.

Ajouté dans la version 3.2.

class contextlib.AsyncContextDecorator

Similar to ContextDecorator but only for asynchronous functions.

Example of AsyncContextDecorator:

from asyncio import run
from contextlib import AsyncContextDecorator

class mycontext(AsyncContextDecorator):
    async def __aenter__(self):
        print('Starting')
        return self

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

The class can then be used like this:

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

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

Ajouté dans la version 3.10.

class contextlib.ExitStack

Gestionnaire de contexte conçu pour simplifier le fait de combiner programmatiquement d'autres gestionnaires de contexte et fonctions de nettoyage, spécifiquement ceux qui sont optionnels ou pilotés par des données d'entrée.

Par exemple, un ensemble de fichiers peut facilement être géré dans une unique instruction with comme suit :

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.

Chaque instance maintient une pile de fonctions de rappels (callbacks) enregistrées qui sont appelées en ordre inverse quand l'instance est fermée (explicitement ou implicitement à la fin d'un bloc with). Notez que ces fonctions ne sont pas invoquées implicitement quand l'instance de la pile de contextes est collectée par le ramasse-miettes.

Ce modèle de pile est utilisé afin que les gestionnaires de contexte qui acquièrent leurs ressources dans leur méthode __init__ (tels que les objets-fichiers) puissent être gérés correctement.

Comme les fonctions de rappel enregistrées sont invoquées dans l'ordre inverse d'enregistrement, cela revient au même que si de multiples blocs with imbriqués avaient été utilisés avec l'ensemble de fonctions enregistrées. Cela s'étend aussi à la gestion d'exceptions — si une fonction de rappel intérieure supprime ou remplace une exception, alors les fonctions extérieures reçoivent des arguments basés sur ce nouvel état.

C'est une API relativement bas-niveau qui s'occupe de dérouler correctement la pile des appels de sortie. Elle fournit une base adaptée pour des gestionnaires de contexte de plus haut niveau qui manipulent la pile de sortie de manière spécifique à l'application.

Ajouté dans la version 3.3.

enter_context(cm)

Enters a new context manager and adds its __exit__() method to the callback stack. The return value is the result of the context manager's own __enter__() method.

Ces gestionnaires de contexte peuvent supprimer des exceptions comme ils le feraient normalement s'ils étaient utilisés directement derrière une instruction with.

Modifié dans la version 3.11: Raises TypeError instead of AttributeError if cm is not a context manager.

push(exit)

Adds a context manager's __exit__() method to the callback stack.

As __enter__ is not invoked, this method can be used to cover part of an __enter__() implementation with a context manager's own __exit__() method.

If passed an object that is not a context manager, this method assumes it is a callback with the same signature as a context manager's __exit__() method and adds it directly to the callback stack.

By returning true values, these callbacks can suppress exceptions the same way context manager __exit__() methods can.

L'objet passé en paramètre est renvoyé par la fonction, ce qui permet à la méthode d'être utilisée comme décorateur de fonction.

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

Accepte une fonction arbitraire et ses arguments et les ajoute à la pile des fonctions de rappel.

À la différence des autres méthodes, les fonctions de rappel ajoutées de cette manière ne peuvent pas supprimer les exceptions (puisqu'elles ne reçoivent jamais les détails de l'exception).

La fonction passée en paramètre est renvoyée par la méthode, ce qui permet à la méthode d'être utilisée comme décorateur de fonction.

pop_all()

Transfère la pile d'appels à une nouvelle instance de ExitStack et la renvoie. Aucune fonction de rappel n'est invoquée par cette opération — à la place, elles sont dorénavant invoquées quand la nouvelle pile sera close (soit explicitement soit implicitement à la fin d'un bloc with).

Par exemple, un groupe de fichiers peut être ouvert comme une opération « tout ou rien » comme suit :

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

Déroule immédiatement la pile d'appels, invoquant les fonctions de rappel dans l'ordre inverse d'enregistrement. Pour chaque gestionnaire de contexte et fonction de sortie enregistré, les arguments passés indiqueront qu'aucune exception n'est survenue.

class contextlib.AsyncExitStack

Un gestionnaire de contexte asynchrone, similaire à ExitStack, apte à combiner à la fois des gestionnaires de contexte synchrones et asynchrones, ainsi que la gestion de coroutines pour la logique de nettoyage.

The close() method is not implemented; aclose() must be used instead.

coroutine enter_async_context(cm)

Similar to ExitStack.enter_context() but expects an asynchronous context manager.

Modifié dans la version 3.11: Raises TypeError instead of AttributeError if cm is not an asynchronous context manager.

push_async_exit(exit)

Similar to ExitStack.push() but expects either an asynchronous context manager or a coroutine function.

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

Similar to ExitStack.callback() but expects a coroutine function.

coroutine aclose()

Similar to ExitStack.close() but properly handles awaitables.

En continuité de l'exemple de 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.

Ajouté dans la version 3.7.

Exemples et Recettes

Cette section décrit quelques exemples et recettes pour décrire une utilisation réelle des outils fournis par contextlib.

Gérer un nombre variable de gestionnaires de contexte

Le cas d'utilisation primaire de ExitStack est celui décrit dans la documentation de la classe : gérer un nombre variable de gestionnaires de contexte et d'autres opérations de nettoyage en une unique instruction with. La variabilité peut venir du nombre de gestionnaires de contexte voulus découlant d'une entrée de l'utilisateur (comme ouvrir une collection spécifique de fichiers de l'utilisateur), ou de certains gestionnaires de contexte qui peuvent être optionnels :

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

Comme montré, ExitStack rend aussi assez facile d'utiliser les instructions with pour gérer des ressources arbitraires qui ne gèrent pas nativement le protocole des gestionnaires de contexte.

Attraper des exceptions depuis les méthodes __enter__

Il est occasionnellement souhaitable d'attraper les exceptions depuis l'implémentation d'une méthode __enter__, sans attraper par inadvertance les exceptions du corps de l'instruction with ou de la méthode __exit__ des gestionnaires de contexte. En utilisant ExitStack, les étapes du protocole des gestionnaires de contexte peuvent être légèrement séparées pour permettre le code suivant :

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

Avoir à faire cela est en fait surtout utile pour indiquer que l'API sous-jacente devrait fournir une interface directe de gestion des ressources à utiliser avec les instructions try/except/finally, mais que toutes les API ne sont pas bien conçues dans cet objectif. Quand un gestionnaire de contexte est la seule API de gestion des ressources fournie, alors ExitStack peut rendre plus facile la gestion de plusieurs situations qui ne peuvent pas être traitées directement dans une instruction with.

Nettoyer dans une méthode __enter__

As noted in the documentation of ExitStack.push(), this method can be useful in cleaning up an already allocated resource if later steps in the __enter__() implementation fail.

Voici un exemple de gestionnaire de contexte qui reçoit des fonctions d'acquisition de ressources et de libération, avec une méthode de validation optionnelle, et qui les adapte au protocole des gestionnaires de contexte :

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

Remplacer un try-finally avec une option variable

Un modèle que vous rencontrerez parfois est un bloc try-finally avec une option pour indiquer si le corps de la clause finally doit être exécuté ou non. Dans sa forme la plus simple (qui ne peut pas déjà être gérée avec juste une clause except), cela ressemble à :

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

Comme avec n'importe quel code basé sur une instruction try, cela peut poser problème pour le développement et la revue, parce que beaucoup de codes d'installation et de nettoyage peuvent finir par être séparés par des sections de code arbitrairement longues.

ExitStack rend possible de plutôt enregistrer une fonction de rappel pour être exécutée à la fin d'une instruction with, et décider ensuite de passer l'exécution de cet appel :

from contextlib import ExitStack

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

This allows the intended cleanup behaviour to be made explicit up front, rather than requiring a separate flag variable.

Si une application particulière utilise beaucoup ce modèle, cela peut-être simplifié encore plus au moyen d'une petite classe d'aide :

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 le nettoyage de la ressource n'est pas déjà soigneusement embarqué dans une fonction autonome, il est possible d'utiliser le décorateur ExitStack.callback() pour déclarer la fonction de nettoyage de ressource en avance :

from contextlib import ExitStack

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

Dû au fonctionnement du protocole des décorateurs, une fonction déclarée ainsi ne peut prendre aucun paramètre. À la place, les ressources à libérer doivent être récupérées depuis l'extérieur comme des variables de fermeture (closure).

Utiliser un gestionnaire de contexte en tant que décorateur de fonction

ContextDecorator rend possible l'utilisation d'un gestionnaire de contexte à la fois ordinairement avec une instruction with ou comme un décorateur de fonction.

Par exemple, il est parfois utile d'emballer les fonctions ou blocs d'instructions avec un journaliseur qui pourrait suivre le temps d'exécution entre l'entrée et la sortie. Plutôt qu'écrire à la fois un décorateur et un gestionnaire de contexte pour la même tâche, hériter de ContextDecorator fournit les deux fonctionnalités en une seule définition :

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)

Les instances de cette classe peuvent être utilisées comme gestionnaires de contexte :

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

Et comme décorateurs de fonctions :

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

Note that there is one additional limitation when using context managers as function decorators: there's no way to access the return value of __enter__(). If that value is needed, then it is still necessary to use an explicit with statement.

Voir aussi

PEP 343 - The "with" statement

La spécification, les motivations et des exemples de l'instruction with en Python.

Gestionnaires de contexte à usage unique, réutilisables et réentrants

La plupart des gestionnaires de contexte sont écrits d'une manière qui ne leur permet que d'être utilisés une fois avec une instruction with. Ces gestionnaires de contexte à usage unique doivent être recréés chaque fois qu'ils sont utilisés — tenter de les utiliser une seconde fois lève une exception ou ne fonctionne pas correctement.

Cette limitation commune signifie qu'il est généralement conseillé de créer les gestionnaires de contexte directement dans l'en-tête du bloc with où ils sont utilisés (comme montré dans tous les exemples d'utilisation au-dessus).

Les fichiers sont un exemple de gestionnaires de contexte étant effectivement à usage unique, puisque la première instruction with ferme le fichier, empêchant d'autres opérations d'entrée/sortie d'être exécutées sur ce fichier.

Les gestionnaires de contexte créés avec contextmanager() sont aussi à usage unique, et se plaindront du fait que le générateur sous-jacent ne produise plus de valeur si vous essayez de les utiliser une seconde fois :

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

Gestionnaires de contexte réentrants

Certains gestionnaires de contexte plus sophistiqués peuvent être « réentrants ». Ces gestionnaires de contexte ne peuvent pas seulement être utilisés avec plusieurs instructions with, mais aussi à l'intérieur d'une instruction with qui utilise déjà ce même gestionnaire de contexte.

threading.RLock is an example of a reentrant context manager, as are suppress(), redirect_stdout(), and chdir(). Here's a very simple example of reentrant use:

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

Les exemples plus réels de réentrance sont susceptibles d'invoquer plusieurs fonctions s'entre-appelant, et donc être bien plus compliqués que cet exemple.

Notez aussi qu'être réentrant ne signifie pas être thread safe. redirect_stdout(), par exemple, n'est définitivement pas thread safe, puisqu'il effectue des changements globaux sur l'état du système en branchant sys.stdout sur différents flux.

Gestionnaires de contexte réutilisables

D'autres gestionnaires de contexte que ceux à usage unique et les réentrants sont les gestionnaires de contexte « réutilisables » (ou, pour être plus explicite, « réutilisables mais pas réentrants », puisque les gestionnaires de contexte réentrants sont aussi réutilisables). Ces gestionnaires de contexte sont conçus afin d'être utilisés plusieurs fois, mais échoueront (ou ne fonctionnent pas correctement) si l'instance de gestionnaire de contexte référencée a déjà été utilisée dans une instruction with englobante.

threading.Lock est un exemple de gestionnaire de contexte réutilisable mais pas réentrant (pour un verrou réentrant, il faut à la place utiliser threading.RLock).

Un autre exemple de gestionnaire de contexte réutilisable mais pas réentrant est ExitStack, puisqu'il invoque toutes les fonctions de rappel actuellement enregistrées en quittant l'instruction with, sans regarder où ces fonctions ont été ajoutées :

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

Comme le montre la sortie de l'exemple, réutiliser une simple pile entre plusieurs instructions with fonctionne correctement, mais essayer de les imbriquer fait que la pile est vidée à la fin du with le plus imbriqué, ce qui n'est probablement pas le comportement voulu.

Pour éviter ce problème, utilisez des instances différentes de ExitStack plutôt qu'une seule instance :

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