29.6. contextlib
— Utilities for with
-statement contexts¶
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.
29.6.1. Utilitaires¶
Fonctions et classes fournies :
-
class
contextlib.
AbstractContextManager
¶ Classe mère abstraite pour les classes qui implémentent les méthodes
object.__enter__()
etobject.__exit__()
. Une implémentation par défaut deobject.__enter__()
est fournie, qui renvoieself
, etobject.__exit__()
est une méthode abstraite qui renvoieNone
par défaut. Voir aussi la définition de Le type gestionnaire de contexte.Nouveau dans la version 3.6.
-
@
contextlib.
contextmanager
¶ Cette fonction est un decorator qui peut être utilisé pour définir une fonction fabriquant des gestionnaires de contexte à utiliser avec
with
, sans nécessiter de créer une classe ou des méthodes__enter__()
et__exit__()
séparées.While many objects natively support use in with statements, sometimes a resource needs to be managed that isn’t a context manager in its own right, and doesn’t implement a
close()
method for use withcontextlib.closing
An abstract example would be the following to ensure correct resource management:
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
The function being decorated must return a generator-iterator when called. This iterator must yield exactly one value, which will be bound to the targets in the
with
statement’sas
clause, if any.At the point where the generator yields, the block nested in the
with
statement is executed. The generator is then resumed after the block is exited. If an unhandled exception occurs in the block, it is reraised inside the generator at the point where the yield occurred. Thus, you can use atry
…except
…finally
statement to trap the error (if any), or ensure that some cleanup takes place. If an exception is trapped merely in order to log it or to perform some action (rather than to suppress it entirely), the generator must reraise that exception. Otherwise the generator context manager will indicate to thewith
statement that the exception has been handled, and execution will resume with the statement immediately following thewith
statement.Le décorateur
contextmanager()
utilise la classeContextDecorator
afin que les gestionnaires de contexte qu’il crée puissent être utilisés aussi bien en tant que décorateurs qu’avec des instructionswith
. 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 parcontextmanager()
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.
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('http://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 blocwith
.
-
contextlib.
suppress
(*exceptions)¶ Renvoie un gestionnaire de contexte qui supprime toutes les exceptions spécifiées si elles surviennent dans le corps du bloc with, et reprend l’exécution sur la première instruction qui suit la fin du bloc with.
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.
Nouveau dans la version 3.4.
-
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.
Par exemple, la sortie de
help()
est normalement envoyée vers sys.stdout. Vous pouvez capturer cette sortie dans une chaîne de caractères en la redirigeant vers un objetio.StringIO
:f = io.StringIO() with redirect_stdout(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.
Nouveau dans la version 3.4.
-
contextlib.
redirect_stderr
(new_target)¶ Similaire à
redirect_stdout()
mais redirigesys.stderr
vers un autre fichier ou objet fichier-compatible.Ce gestionnaire de contexte est réentrant.
Nouveau dans la version 3.5.
-
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é parcontextmanager()
, 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 >>> @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
As the decorated function must be able to be called multiple times, the underlying context manager must support use in multiple
with
statements. If this is not the case, then the original construct with the explicitwith
statement inside the function should be used.Nouveau dans la version 3.2.
-
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
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.
Nouveau dans la version 3.3.
-
enter_context
(cm)¶ Entre dans un nouveau gestionnaire de contexte et ajoute sa méthode
__exit__()
à la pile d’appels. La valeur de retour est le résultat de la méthode__enter__()
du gestionnaire de contexte donné.Ces gestionnaires de contexte peuvent supprimer des exceptions comme ils le feraient normalement s’ils étaient utilisés directement derrière une instruction
with
.
-
push
(exit)¶ Ajoute la méthode
__exit__()
d’un gestionnaire de contexte à la pile d’appels.Comme
__enter__
n’est pas invoquée, cette méthode peut être utilisée pour couvrir une partie de l’implémentation de__enter__()
avec la propre méthode__exit__()
d’un gestionnaire de contexte.Si l’argument passé n’est pas un gestionnaire de contexte, la méthode assume qu’il s’agit d’une fonction de rappel avec la même signature que la méthode
__exit__()
des gestionnaires de contexte pour l’ajouter directement à la pile d’appels.En retournant des valeurs vraies, ces fonctions peuvent supprimer des exceptions de la même manière que le peuvent les méthodes
__exit__()
des gestionnaires de contexte.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 blocwith
).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.
-
29.6.2. Exemples et Recettes¶
Cette section décrit quelques exemples et recettes pour décrire une utilisation réelle des outils fournis par contextlib
.
29.6.2.1. 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.
29.6.2.2. Simplifying support for single optional context managers¶
In the specific case of a single optional context manager, ExitStack
instances can be used as a « do nothing » context manager, allowing a context
manager to easily be omitted without affecting the overall structure of
the source code:
def debug_trace(details):
if __debug__:
return TraceContext(details)
# Don't do anything special with the context in release mode
return ExitStack()
with debug_trace():
# Suite is traced in debug mode, but runs normally otherwise
29.6.2.3. 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
.
29.6.2.4. Nettoyer dans une méthode __enter__
¶
Comme indiqué dans la documentation de ExitStack.push()
, cette méthode peut être utile pour nettoyer une ressource déjà allouée si les dernières étapes de l’implémentation de __enter__()
échouent.
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()
29.6.2.5. 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()
Cela permet de rendre explicite dès le départ le comportement de nettoyage attendu, plutôt que de nécessiter une option séparée.
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(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 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).
29.6.2.6. 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()
Notez qu’il y a une autre limitation en utilisant les gestionnaires de contexte comme décorateurs : il n’y a aucune manière d’accéder à la valeur de retour de __enter__()
. Si cette valeur est nécessaire, il faut utiliser explicitement une instruction with
.
29.6.3. 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
29.6.3.1. Gestionnaires de contexte réentrants¶
More sophisticated context managers may be « reentrant ». These context
managers can not only be used in multiple with
statements,
but may also be used inside a with
statement that is already
using the same context manager.
threading.RLock
est un exemple de gestionnaire de contexte réentrant, comme le sont aussi suppress()
et redirect_stdout()
. Voici un très simple exemple d’utilisation réentrante :
>>> 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.
29.6.3.2. 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