"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
   "try"…"except"…"finally" 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
