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__() und object.__exit__() implementieren. Eine Standardimplementierung für object.__enter__() wird bereitgestellt, die self zurückgibt, während object.__exit__() eine abstrakte Methode ist, die standardmäßig None zurü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__() und object.__aexit__() implementieren. Eine Standardimplementierung für object.__aenter__() wird bereitgestellt, die self zurückgibt, während object.__aexit__() eine abstrakte Methode ist, die standardmäßig None zurü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 mit contextlib.closing implementiert.

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 der with-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 eine try...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 der with-Anweisung, dass die Ausnahme behandelt wurde, und die Ausführung wird mit der Anweisung unmittelbar nach der with-Anweisung fortgesetzt.

contextmanager() verwendet ContextDecorator, sodass die von ihm erstellten Context-Manager sowohl als Decorators als auch in with-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 von contextmanager() 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 mit async with-Anweisungen verwendet werden

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

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 page explizit schließen zu müssen. Selbst wenn ein Fehler auftritt, wird page.close() aufgerufen, wenn der with-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 ist closing() am nützlichsten für Drittanbieter-Typen, die keine Context-Manager unterstützen. Dieses Beispiel dient rein illustrativen Zwecken, da urlopen() 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 zu

from 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 durch break oder eine Ausnahme verlassen werden. Zum Beispiel

from 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 der with-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 eine BaseExceptionGroup auslö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 der derive()-Methode der ursprünglichen Gruppe erstellt wird.

Hinzugefügt in Version 3.4.

Geändert in Version 3.12: suppress unterstützt jetzt das Unterdrücken von Ausnahmen, die als Teil einer BaseExceptionGroup ausgelöst werden.

contextlib.redirect_stdout(new_target)

Context Manager zum temporären Umleiten von sys.stdout auf 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 ein io.StringIO-Objekt umleiten. Der Ersatzstream wird von der __enter__-Methode zurückgegeben und ist somit als Ziel der with-Anweisung verfügbar

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

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

Um die Ausgabe von help() an sys.stderr zu senden

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

Beachten Sie, dass die globale Nebenwirkung auf sys.stdout bedeutet, 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 leitet sys.stderr auf 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 ContextDecorator abgeleitete Context-Manager müssen __enter__ und __exit__ wie gewohnt implementieren. __exit__ behält seine optionale Ausnahmebehandlung auch bei Verwendung als Decorator bei.

ContextDecorator wird von contextmanager() verwendet, sodass Sie diese Funktionalität automatisch erhalten.

Beispiel für ContextDecorator

from 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

ContextDecorator lässt Sie stattdessen schreiben

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

Es macht deutlich, dass cm fü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 ContextDecorator als Mixin-Klasse erweitert werden

from 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 expliziten with-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 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

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 die ExitStack-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 TypeError statt AttributeError aus, 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 einer with-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 muss aclose() 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 TypeError anstelle von AttributeError aus, 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.

Siehe auch

PEP 343 - Die "with"-Anweisung

Die Spezifikation, Hintergrund und Beispiele für die Python with-Anweisung.

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