contextlib — Dienstprogramme für with-Anweisungen¶
Quellcode: Lib/contextlib.py
Dieses Modul bietet Dienstprogramme für gängige Aufgaben im Zusammenhang mit der with-Anweisung. Weitere Informationen finden Sie auch unter Context Manager Typen und With Statement Context Manager.
Dienstprogramme¶
Bereitgestellte Funktionen und Klassen
- class contextlib.AbstractContextManager¶
Eine abstrakte Basisklasse für Klassen, die
object.__enter__()undobject.__exit__()implementieren. Eine Standardimplementierung fürobject.__enter__()wird bereitgestellt, dieselfzurückgibt, währendobject.__exit__()eine abstrakte Methode ist, die standardmäßigNonezurückgibt. Siehe auch die Definition von Context Manager Typen.Hinzugefügt in Version 3.6.
- class contextlib.AbstractAsyncContextManager¶
Eine abstrakte Basisklasse für Klassen, die
object.__aenter__()undobject.__aexit__()implementieren. Eine Standardimplementierung fürobject.__aenter__()wird bereitgestellt, dieselfzurückgibt, währendobject.__aexit__()eine abstrakte Methode ist, die standardmäßigNonezurückgibt. Siehe auch die Definition von Asynchrone Context Manager.Hinzugefügt in Version 3.7.
- @contextlib.contextmanager¶
Diese Funktion ist ein Decorator, der verwendet werden kann, um eine Factory-Funktion für
with-Anweisungs-Context-Manager zu definieren, ohne eine Klasse oder separate__enter__()- und__exit__()-Methoden erstellen zu müssen.Während viele Objekte die Verwendung in with-Anweisungen nativ unterstützen, muss manchmal eine Ressource verwaltet werden, die selbst kein Context Manager ist und keine
close()-Methode zur Verwendung mitcontextlib.closingimplementiert.Ein abstraktes Beispiel wäre das Folgende, um eine korrekte Ressourcenverwaltung zu gewährleisten
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)
Die Funktion kann dann wie folgt verwendet werden
>>> 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
Die dekorierte Funktion muss bei Aufruf einen Generator-Iterator zurückgeben. Dieser Iterator muss genau einen Wert liefern, der an die Ziele in der
as-Klausel derwith-Anweisung gebunden wird, falls vorhanden.An dem Punkt, an dem der Generator einen Wert liefert, wird der Block, der in der
with-Anweisung verschachtelt ist, ausgeführt. Der Generator wird dann nach Verlassen des Blocks fortgesetzt. Wenn im Block eine unbehandelte Ausnahme auftritt, wird sie innerhalb des Generators an der Stelle, an der die Yield-Anweisung auftrat, erneut ausgelöst. Daher können Sie einetry...except...finally-Anweisung verwenden, um den Fehler (falls vorhanden) abzufangen oder sicherzustellen, dass einige Bereinigungen stattfinden. Wenn eine Ausnahme nur abgefangen wird, um sie zu protokollieren oder eine Aktion durchzuführen (anstatt sie vollständig zu unterdrücken), muss der Generator die Ausnahme erneut auslösen. Andernfalls signalisiert der Generator-Context-Manager derwith-Anweisung, dass die Ausnahme behandelt wurde, und die Ausführung wird mit der Anweisung unmittelbar nach derwith-Anweisung fortgesetzt.contextmanager()verwendetContextDecorator, sodass die von ihm erstellten Context-Manager sowohl als Decorators als auch inwith-Anweisungen verwendet werden können. Wenn sie als Decorator verwendet werden, wird bei jedem Funktionsaufruf implizit eine neue Generatorinstanz erstellt (dies ermöglicht es den ansonsten "einmaligen" Context-Managern, die voncontextmanager()erstellt werden, die Anforderung zu erfüllen, dass Context-Manager mehrere Aufrufe unterstützen, um als Decorators verwendet werden zu können).Geändert in Version 3.2: Verwendung von
ContextDecorator.
- @contextlib.asynccontextmanager¶
Ähnlich wie
contextmanager(), erstellt aber einen asynchronen Context Manager.Diese Funktion ist ein Decorator, der verwendet werden kann, um eine Factory-Funktion für
async with-Anweisungs-asynchrone Context-Manager zu definieren, ohne eine Klasse oder separate__aenter__()- und__aexit__()-Methoden erstellen zu müssen. Sie muss auf eine asynchrone Generatorfunktion angewendet werden.Ein einfaches Beispiel
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 ...')
Hinzugefügt in Version 3.7.
Mit
asynccontextmanager()definierte Context-Manager können entweder als Decorators oder mitasync with-Anweisungen verwendet werdenimport 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 ...
Wenn sie als Decorator verwendet werden, wird bei jedem Funktionsaufruf implizit eine neue Generatorinstanz erstellt. Dies ermöglicht es den ansonsten "einmaligen" Context-Managern, die von
asynccontextmanager()erstellt werden, die Anforderung zu erfüllen, dass Context-Manager mehrere Aufrufe unterstützen, um als Decorators verwendet werden zu können.Geändert in Version 3.10: Asynchrone Context-Manager, die mit
asynccontextmanager()erstellt wurden, können als Decorators verwendet werden.
- contextlib.closing(thing)¶
Gibt einen Context Manager zurück, der thing nach Abschluss des Blocks schließt. Dies ist im Grunde äquivalent zu
from contextlib import contextmanager @contextmanager def closing(thing): try: yield thing finally: thing.close()
Und ermöglicht Ihnen, Code wie diesen zu schreiben
from contextlib import closing from urllib.request import urlopen with closing(urlopen('https://pythonlang.de')) as page: for line in page: print(line)
ohne
pageexplizit schließen zu müssen. Selbst wenn ein Fehler auftritt, wirdpage.close()aufgerufen, wenn derwith-Block verlassen wird.Hinweis
Die meisten Ressourcen verwaltenden Typen unterstützen das Context Manager-Protokoll, das thing beim Verlassen der
with-Anweisung schließt. Daher istclosing()am nützlichsten für Drittanbieter-Typen, die keine Context-Manager unterstützen. Dieses Beispiel dient rein illustrativen Zwecken, daurlopen()normalerweise in einem Context Manager verwendet würde.
- contextlib.aclosing(thing)¶
Gibt einen asynchronen Context Manager zurück, der die Methode
aclose()von thing nach Abschluss des Blocks aufruft. Dies ist im Grunde äquivalent zufrom contextlib import asynccontextmanager @asynccontextmanager async def aclosing(thing): try: yield thing finally: await thing.aclose()
Wichtig ist, dass
aclosing()deterministisches Aufräumen von asynchronen Generatoren unterstützt, wenn diese vorzeitig durchbreakoder eine Ausnahme verlassen werden. Zum Beispielfrom contextlib import aclosing async with aclosing(my_generator()) as values: async for value in values: if value == 42: break
Dieses Muster stellt sicher, dass der asynchrone Exit-Code des Generators im selben Kontext wie seine Iterationen ausgeführt wird (damit Ausnahmen und Kontextvariablen wie erwartet funktionieren und der Exit-Code nicht nach der Lebensdauer einer Aufgabe ausgeführt wird, von der er abhängt).
Hinzugefügt in Version 3.10.
- contextlib.nullcontext(enter_result=None)¶
Gibt einen Context Manager zurück, der enter_result von
__enter__zurückgibt, aber ansonsten nichts tut. Er ist dafür vorgesehen, als Platzhalter für einen optionalen Context Manager verwendet zu werden, z. B.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
Ein Beispiel, das enter_result verwendet
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
Er kann auch als Platzhalter für asynchrone Context-Manager verwendet werden
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
Hinzugefügt in Version 3.7.
Geändert in Version 3.10: Unterstützung für asynchrone Context-Manager wurde hinzugefügt.
- contextlib.suppress(*exceptions)¶
Gibt einen Context Manager zurück, der alle angegebenen Ausnahmen unterdrückt, wenn sie im Körper einer
with-Anweisung auftreten, und dann die Ausführung mit der ersten Anweisung nach dem Ende derwith-Anweisung fortsetzt.Wie bei jedem anderen Mechanismus, der Ausnahmen vollständig unterdrückt, sollte dieser Context Manager nur verwendet werden, um sehr spezifische Fehler abzudecken, bei denen ein stilles Fortfahren mit der Programmausführung als richtig anerkannt wird.
Zum Beispiel
from contextlib import suppress with suppress(FileNotFoundError): os.remove('somefile.tmp') with suppress(FileNotFoundError): os.remove('someotherfile.tmp')
Dieser Code ist äquivalent zu
try: os.remove('somefile.tmp') except FileNotFoundError: pass try: os.remove('someotherfile.tmp') except FileNotFoundError: pass
Dieser Context Manager ist reentrant.
Wenn der Code innerhalb des
with-Blocks eineBaseExceptionGroupauslöst, werden unterdrückte Ausnahmen aus der Gruppe entfernt. Alle Ausnahmen der Gruppe, die nicht unterdrückt werden, werden in einer neuen Gruppe erneut ausgelöst, die mit derderive()-Methode der ursprünglichen Gruppe erstellt wird.Hinzugefügt in Version 3.4.
Geändert in Version 3.12:
suppressunterstützt jetzt das Unterdrücken von Ausnahmen, die als Teil einerBaseExceptionGroupausgelöst werden.
- contextlib.redirect_stdout(new_target)¶
Context Manager zum temporären Umleiten von
sys.stdoutauf eine andere Datei oder ein dateiähnliches Objekt.Dieses Werkzeug fügt Flexibilität zu bestehenden Funktionen oder Klassen hinzu, deren Ausgabe fest an stdout gebunden ist. Sie können diese Ausgabe in einem String erfassen, indem Sie die Ausgabe in ein
io.StringIO-Objekt umleiten.Zum Beispiel wird die Ausgabe von
help()normalerweise an sys.stdout gesendet. Sie können diese Ausgabe in einem String erfassen, indem Sie die Ausgabe in einio.StringIO-Objekt umleiten. Der Ersatzstream wird von der__enter__-Methode zurückgegeben und ist somit als Ziel derwith-Anweisung verfügbarwith redirect_stdout(io.StringIO()) as f: help(pow) s = f.getvalue()
Um die Ausgabe von
help()in eine Datei auf der Festplatte zu schreiben, leiten Sie die Ausgabe in eine reguläre Datei umwith open('help.txt', 'w') as f: with redirect_stdout(f): help(pow)
Um die Ausgabe von
help()an sys.stderr zu sendenwith redirect_stdout(sys.stderr): help(pow)
Beachten Sie, dass die globale Nebenwirkung auf
sys.stdoutbedeutet, dass dieser Context Manager nicht für die Verwendung in Bibliotheks-Code und den meisten Threaded-Anwendungen geeignet ist. Er hat auch keine Auswirkungen auf die Ausgabe von Unterprozessen. Er ist jedoch für viele Dienstprogramm-Skripte immer noch ein nützlicher Ansatz.Dieser Context Manager ist reentrant.
Hinzugefügt in Version 3.4.
- contextlib.redirect_stderr(new_target)¶
Ähnlich wie
redirect_stdout(), aber leitetsys.stderrauf eine andere Datei oder ein dateiähnliches Objekt um.Dieser Context Manager ist reentrant.
Hinzugefügt in Version 3.5.
- contextlib.chdir(path)¶
Nicht parallel-sicherer Context Manager zum Ändern des aktuellen Arbeitsverzeichnisses. Da dies einen globalen Zustand, das Arbeitsverzeichnis, ändert, ist es nicht für die Verwendung in den meisten Threaded- oder asynchronen Kontexten geeignet. Es ist auch nicht für die meisten nicht-linearen Codeausführungen, wie Generatoren, geeignet, bei denen die Programmausführung vorübergehend aufgegeben wird – es sei denn, dies ist ausdrücklich gewünscht, sollten Sie nicht yielden, wenn dieser Context Manager aktiv ist.
Dies ist ein einfacher Wrapper um
chdir(). Er ändert das aktuelle Arbeitsverzeichnis beim Betreten und stellt das alte beim Verlassen wieder her.Dieser Context Manager ist reentrant.
Hinzugefügt in Version 3.11.
- class contextlib.ContextDecorator¶
Eine Basisklasse, die es einem Context Manager ermöglicht, auch als Decorator verwendet zu werden.
Von
ContextDecoratorabgeleitete Context-Manager müssen__enter__und__exit__wie gewohnt implementieren.__exit__behält seine optionale Ausnahmebehandlung auch bei Verwendung als Decorator bei.ContextDecoratorwird voncontextmanager()verwendet, sodass Sie diese Funktionalität automatisch erhalten.Beispiel für
ContextDecoratorfrom contextlib import ContextDecorator class mycontext(ContextDecorator): def __enter__(self): print('Starting') return self def __exit__(self, *exc): print('Finishing') return False
Die Klasse kann dann wie folgt verwendet werden
>>> @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
Diese Änderung ist nur syntaktischer Zucker für jedes Konstrukt der folgenden Form
def f(): with cm(): # Do stuff
ContextDecoratorlässt Sie stattdessen schreiben@cm() def f(): # Do stuff
Es macht deutlich, dass
cmfür die gesamte Funktion gilt und nicht nur für einen Teil davon (und das Einsparen einer Einrückungsebene ist ebenfalls nett).Bestehende Context-Manager, die bereits eine Basisklasse haben, können durch Verwendung von
ContextDecoratorals Mixin-Klasse erweitert werdenfrom contextlib import ContextDecorator class mycontext(ContextBaseClass, ContextDecorator): def __enter__(self): return self def __exit__(self, *exc): return False
Hinweis
Da die dekorierte Funktion mehrmals aufgerufen werden können muss, muss der zugrunde liegende Context Manager die Verwendung in mehreren
with-Anweisungen unterstützen. Wenn dies nicht der Fall ist, sollte die ursprüngliche Konstruktion mit der explizitenwith-Anweisung innerhalb der Funktion verwendet werden.Hinzugefügt in Version 3.2.
- class contextlib.AsyncContextDecorator¶
Ähnlich wie
ContextDecorator, aber nur für asynchrone Funktionen.Beispiel für
AsyncContextDecoratorfrom 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
Die Klasse kann dann wie folgt verwendet werden
>>> @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
Hinzugefügt in Version 3.10.
- class contextlib.ExitStack¶
Ein Context Manager, der entwickelt wurde, um das programmatische Kombinieren anderer Context Manager und Bereinigungsfunktionen zu erleichtern, insbesondere solcher, die optional sind oder anderweitig durch Eingabedaten gesteuert werden.
Zum Beispiel kann eine Reihe von Dateien in einer einzigen with-Anweisung wie folgt einfach behandelt werden
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
Die
__enter__()-Methode gibt dieExitStack-Instanz zurück und führt keine zusätzlichen Operationen durch.Jede Instanz verwaltet einen Stapel registrierter Rückruffunktionen, die in umgekehrter Reihenfolge aufgerufen werden, wenn die Instanz geschlossen wird (entweder explizit oder implizit am Ende einer
with-Anweisung). Beachten Sie, dass Rückruffunktionen *nicht* implizit aufgerufen werden, wenn die Context-Stack-Instanz garbage collected wird.Dieses Stack-Modell wird verwendet, damit Context-Manager, die ihre Ressourcen in ihrer
__init__-Methode erwerben (wie z. B. Dateiobjekte), korrekt behandelt werden können.Da registrierte Rückruffunktionen in umgekehrter Registrierungsreihenfolge aufgerufen werden, verhält sich dies so, als ob mehrere verschachtelte
with-Anweisungen mit dem registrierten Satz von Rückruffunktionen verwendet worden wären. Dies erstreckt sich sogar auf die Ausnahmebehandlung – wenn eine innere Rückruffunktion eine Ausnahme unterdrückt oder ersetzt, werden äußere Rückruffunktionen mit Argumenten basierend auf diesem aktualisierten Zustand übergeben.Dies ist eine relativ Low-Level-API, die sich um die Details der korrekten Entladung des Stacks von Exit-Rückruffunktionen kümmert. Sie bietet eine geeignete Grundlage für höherwertige Context-Manager, die den Exit-Stack anwendungsspezifisch manipulieren.
Hinzugefügt in Version 3.3.
- enter_context(cm)¶
Betritt einen neuen Context Manager und fügt seine
__exit__()-Methode dem Callback-Stack hinzu. Der Rückgabewert ist das Ergebnis der eigenen__enter__()-Methode des Context Managers.Diese Context-Manager können Ausnahmen unterdrücken, genau wie sie es normalerweise tun würden, wenn sie direkt als Teil einer
with-Anweisung verwendet würden.Geändert in Version 3.11: Löst
TypeErrorstattAttributeErroraus, wenn cm kein Context Manager ist.
- push(exit)¶
Fügt die
__exit__()-Methode eines Context Managers dem Callback-Stack hinzu.Da
__enter__*nicht* aufgerufen wird, kann diese Methode verwendet werden, um einen Teil einer__enter__()-Implementierung mit der eigenen__exit__()-Methode eines Context Managers abzudecken.Wenn ein Objekt übergeben wird, das kein Context Manager ist, geht diese Methode davon aus, dass es sich um eine Rückruffunktion mit derselben Signatur wie die
__exit__()-Methode eines Context Managers handelt und fügt sie direkt dem Callback-Stack hinzu.Durch Rückgabe von wahren Werten können diese Rückruffunktionen Ausnahmen unterdrücken, genauso wie es Context Manager
__exit__()-Methoden können.Das übergebene Objekt wird von der Funktion zurückgegeben, wodurch diese Methode als Funktionsdecorator verwendet werden kann.
- callback(callback, /, *args, **kwds)¶
Akzeptiert eine beliebige Rückruffunktion und Argumente und fügt sie dem Callback-Stack hinzu.
Im Gegensatz zu den anderen Methoden können auf diese Weise hinzugefügte Rückruffunktionen keine Ausnahmen unterdrücken (da ihnen niemals die Ausnahmedetails übergeben werden).
Die übergebene Rückruffunktion wird von der Funktion zurückgegeben, wodurch diese Methode als Funktionsdecorator verwendet werden kann.
- pop_all()¶
Überträgt den Callback-Stack auf eine neue
ExitStack-Instanz und gibt diese zurück. Bei dieser Operation werden keine Rückruffunktionen aufgerufen – stattdessen werden sie nun aufgerufen, wenn der neue Stack geschlossen wird (entweder explizit oder implizit am Ende einerwith-Anweisung).Zum Beispiel kann eine Gruppe von Dateien als "Alles oder Nichts"-Operation wie folgt geöffnet werden
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()¶
Entlädt den Callback-Stack sofort und ruft Rückruffunktionen in umgekehrter Registrierungsreihenfolge auf. Für alle registrierten Context-Manager und Exit-Callbacks geben die übergebenen Argumente an, dass keine Ausnahme aufgetreten ist.
- class contextlib.AsyncExitStack¶
Ein asynchroner Kontextmanager, ähnlich wie
ExitStack, der die Kombination von synchronen und asynchronen Kontextmanagern unterstützt und auch Koroutinen für Bereinigungslogiken bereitstellt.Die Methode
close()ist nicht implementiert; stattdessen mussaclose()verwendet werden.- async enter_async_context(cm)¶
Ähnlich wie
ExitStack.enter_context(), erwartet aber einen asynchronen Kontextmanager.Geändert in Version 3.11: Löst
TypeErroranstelle vonAttributeErroraus, wenn cm kein asynchroner Kontextmanager ist.
- push_async_exit(exit)¶
Ähnlich wie
ExitStack.push(), erwartet aber entweder einen asynchronen Kontextmanager oder eine Koroutinenfunktion.
- push_async_callback(callback, /, *args, **kwds)¶
Ähnlich wie
ExitStack.callback(), erwartet aber eine Koroutinenfunktion.
- async aclose()¶
Ähnlich wie
ExitStack.close(), behandelt aber awaitables korrekt.
Fortsetzung des Beispiels für
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.
Hinzugefügt in Version 3.7.
Beispiele und Rezepte¶
Dieser Abschnitt beschreibt einige Beispiele und Rezepte für die effektive Nutzung der von contextlib bereitgestellten Werkzeuge.
Unterstützung einer variablen Anzahl von Kontextmanagern¶
Der primäre Anwendungsfall für ExitStack ist derjenige, der in der Klassendokumentation angegeben ist: Unterstützung einer variablen Anzahl von Kontextmanagern und anderen Bereinigungsoperationen in einer einzigen with-Anweisung. Die Variabilität kann aus der Anzahl der benötigten Kontextmanager resultieren, die durch Benutzereingaben bestimmt wird (z.B. Öffnen einer vom Benutzer spezifizierten Sammlung von Dateien), oder daraus, dass einige der Kontextmanager optional sind.
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
Wie gezeigt, erleichtert ExitStack auch die Verwendung von with-Anweisungen zur Verwaltung beliebiger Ressourcen, die das Kontextmanager-Protokoll nicht nativ unterstützen.
Ausnahmen von __enter__-Methoden abfangen¶
Es ist gelegentlich wünschenswert, Ausnahmen von einer __enter__-Methodenimplementierung abzufangen, *ohne* dabei versehentlich Ausnahmen aus dem Körper der with-Anweisung oder der __exit__-Methode des Kontextmanagers abzufangen. Durch die Verwendung von ExitStack können die Schritte des Kontextmanagement-Protokolls leicht getrennt werden, um dies zu ermöglichen.
stack = ExitStack()
try:
x = stack.enter_context(cm)
except Exception:
# handle __enter__ exception
else:
with stack:
# Handle normal case
Das tatsächliche Bedürfnis, dies zu tun, deutet wahrscheinlich darauf hin, dass die zugrunde liegende API eine direkte Ressourcemanagement-Schnittstelle für die Verwendung mit try/except/finally-Anweisungen bereitstellen sollte, aber nicht alle APIs sind in dieser Hinsicht gut gestaltet. Wenn ein Kontextmanager die einzige bereitgestellte Ressourcemanagement-API ist, kann ExitStack die Handhabung verschiedener Situationen erleichtern, die nicht direkt in einer with-Anweisung behandelt werden können.
Bereinigung in einer __enter__-Implementierung¶
Wie in der Dokumentation von ExitStack.push() erwähnt, kann diese Methode nützlich sein, um eine bereits zugewiesene Ressource zu bereinigen, wenn spätere Schritte in der __enter__()-Implementierung fehlschlagen.
Hier ist ein Beispiel dafür, wie dies für einen Kontextmanager durchgeführt wird, der Funktionen zur Ressourcenbeschaffung und -freigabe sowie eine optionale Validierungsfunktion akzeptiert und sie dem Kontextmanagement-Protokoll zuordnet.
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()
Ersetzen von Verwendungen von try-finally und Flag-Variablen¶
Ein Muster, das man manchmal sieht, ist eine try-finally-Anweisung mit einer Flag-Variablen, die angibt, ob der Körper der finally-Klausel ausgeführt werden soll oder nicht. In seiner einfachsten Form (die nicht bereits durch die Verwendung einer except-Klausel stattdessen gehandhabt werden kann), sieht sie etwa so aus.
cleanup_needed = True
try:
result = perform_operation()
if result:
cleanup_needed = False
finally:
if cleanup_needed:
cleanup_resources()
Wie bei jedem Code, der auf try-Anweisungen basiert, kann dies zu Problemen bei der Entwicklung und Überprüfung führen, da der Setup-Code und der Cleanup-Code durch beliebig lange Codeabschnitte getrennt sein können.
ExitStack ermöglicht es, stattdessen einen Callback für die Ausführung am Ende einer with-Anweisung zu registrieren und dann später zu entscheiden, die Ausführung dieses Callbacks zu überspringen.
from contextlib import ExitStack
with ExitStack() as stack:
stack.callback(cleanup_resources)
result = perform_operation()
if result:
stack.pop_all()
Dadurch kann das beabsichtigte Cleanup-Verhalten von vorneherein explizit gemacht werden, anstatt eine separate Flag-Variable zu benötigen.
Wenn eine bestimmte Anwendung dieses Muster häufig verwendet, kann es durch eine kleine Hilfsklasse noch weiter vereinfacht werden.
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()
Wenn die Ressourcenbereinigung nicht bereits sauber in einer eigenständigen Funktion gebündelt ist, ist es dennoch möglich, die Dekoratorform von ExitStack.callback() zu verwenden, um die Ressourcenbereinigung im Voraus zu deklarieren.
from contextlib import ExitStack
with ExitStack() as stack:
@stack.callback
def cleanup_resources():
...
result = perform_operation()
if result:
stack.pop_all()
Aufgrund der Funktionsweise des Dekoratorprotokolls kann eine auf diese Weise deklarierte Callback-Funktion keine Parameter entgegennehmen. Stattdessen müssen alle freizugebenden Ressourcen als Closure-Variablen zugegriffen werden.
Verwendung eines Kontextmanagers als Funktionsdekorator¶
ContextDecorator ermöglicht die Verwendung eines Kontextmanagers sowohl in einer normalen with-Anweisung als auch als Funktionsdekorator.
Es ist zum Beispiel manchmal nützlich, Funktionen oder Gruppen von Anweisungen mit einem Logger zu umschließen, der die Zeit des Eintritts und Austritts verfolgen kann. Anstatt sowohl einen Funktionsdekorator als auch einen Kontextmanager für die Aufgabe zu schreiben, bietet das Erben von ContextDecorator beide Fähigkeiten in einer einzigen Definition.
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)
Instanzen dieser Klasse können sowohl als Kontextmanager verwendet werden.
with track_entry_and_exit('widget loader'):
print('Some time consuming activity goes here')
load_widget()
Und auch als Funktionsdekorator.
@track_entry_and_exit('widget loader')
def activity():
print('Some time consuming activity goes here')
load_widget()
Beachten Sie, dass es eine zusätzliche Einschränkung gibt, wenn Kontextmanager als Funktionsdekoratoren verwendet werden: Es gibt keine Möglichkeit, auf den Rückgabewert von __enter__() zuzugreifen. Wenn dieser Wert benötigt wird, ist es weiterhin notwendig, eine explizite with-Anweisung zu verwenden.
Einzelnutzungs-, wiederverwendbare und reentrante Kontextmanager¶
Die meisten Kontextmanager sind so geschrieben, dass sie nur einmal effektiv in einer with-Anweisung verwendet werden können. Diese einmalig verwendbaren Kontextmanager müssen bei jeder Verwendung neu erstellt werden - ein erneuter Versuch wird eine Ausnahme auslösen oder andernfalls nicht korrekt funktionieren.
Diese übliche Einschränkung bedeutet, dass es generell ratsam ist, Kontextmanager direkt in der Kopfzeile der with-Anweisung zu erstellen, wo sie verwendet werden (wie in allen obigen Anwendungsbeispielen gezeigt).
Dateien sind ein Beispiel für effektiv einmalig verwendbare Kontextmanager, da die erste with-Anweisung die Datei schließt und weitere E/A-Operationen mit diesem Datei-Objekt verhindert.
Kontextmanager, die mit contextmanager() erstellt wurden, sind ebenfalls einmalig verwendbare Kontextmanager und melden einen Fehler, wenn der zugrunde liegende Generator beim zweiten Versuch nicht liefert.
>>> 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
Reentrante Kontextmanager¶
Anspruchsvollere Kontextmanager können "reentrant" sein. Diese Kontextmanager können nicht nur in mehreren with-Anweisungen verwendet werden, sondern auch *innerhalb* einer with-Anweisung, die bereits denselben Kontextmanager verwendet.
threading.RLock ist ein Beispiel für einen reentranten Kontextmanager, ebenso wie suppress(), redirect_stdout() und chdir(). Hier ist ein sehr einfaches Beispiel für die reentrante Verwendung.
>>> 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
Reale Beispiele für Reentrancy beinhalten eher mehrere Funktionen, die sich gegenseitig aufrufen, und sind daher weitaus komplizierter als dieses Beispiel.
Beachten Sie auch, dass reentrant *nicht* dasselbe ist wie thread-sicher. redirect_stdout() zum Beispiel ist definitiv nicht thread-sicher, da es eine globale Änderung des Systemzustands vornimmt, indem es sys.stdout an einen anderen Stream bindet.
Wiederverwendbare Kontextmanager¶
Abweichend von einmalig verwendbaren und reentranten Kontextmanagern sind "wiederverwendbare" Kontextmanager (oder, um ganz explizit zu sein, "wiederverwendbare, aber nicht reentrante" Kontextmanager, da reentrante Kontextmanager ebenfalls wiederverwendbar sind). Diese Kontextmanager unterstützen die mehrfache Verwendung, schlagen jedoch fehl (oder funktionieren andernfalls nicht korrekt), wenn die spezifische Kontextmanager-Instanz bereits in einer enthaltenden with-Anweisung verwendet wurde.
threading.Lock ist ein Beispiel für einen wiederverwendbaren, aber nicht reentranten Kontextmanager (für ein reentrant Lock muss stattdessen threading.RLock verwendet werden).
Ein weiteres Beispiel für einen wiederverwendbaren, aber nicht reentranten Kontextmanager ist ExitStack, da er beim Verlassen jeder with-Anweisung *alle* aktuell registrierten Callbacks aufruft, unabhängig davon, wo diese Callbacks hinzugefügt wurden.
>>> 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
Wie die Ausgabe des Beispiels zeigt, funktioniert die Wiederverwendung eines einzelnen Stack-Objekts über mehrere with-Anweisungen hinweg korrekt, aber der Versuch, sie zu verschachteln, führt dazu, dass der Stack am Ende der innersten with-Anweisung gelöscht wird, was unwahrscheinlich das gewünschte Verhalten ist.
Die Verwendung separater ExitStack-Instanzen anstelle der Wiederverwendung einer einzigen Instanz vermeidet dieses Problem.
>>> 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