contextvars — Kontextvariablen


Dieses Modul stellt APIs zur Verwaltung, Speicherung und zum Zugriff auf kontextlokalen Zustand bereit. Die Klasse ContextVar wird verwendet, um Kontextvariablen zu deklarieren und mit ihnen zu arbeiten. Die Funktion copy_context() und die Klasse Context sollten zur Verwaltung des aktuellen Kontexts in asynchronen Frameworks verwendet werden.

Kontextmanager, die einen Zustand haben, sollten Kontextvariablen anstelle von threading.local() verwenden, um zu verhindern, dass ihr Zustand unerwartet in anderen Code überläuft, wenn er in nebenläufigem Code verwendet wird.

Siehe auch PEP 567 für weitere Details.

Hinzugefügt in Version 3.7.

Kontextvariablen

class contextvars.ContextVar(name[, *, default])

Diese Klasse wird verwendet, um eine neue Kontextvariable zu deklarieren, z.B.

var: ContextVar[int] = ContextVar('var', default=42)

Der erforderliche Parameter name wird für Introspektions- und Debugging-Zwecke verwendet.

Der optionale Keyword-only Parameter default wird von ContextVar.get() zurückgegeben, wenn kein Wert für die Variable im aktuellen Kontext gefunden wird.

Wichtig: Kontextvariablen sollten auf der obersten Modulebene erstellt werden und niemals in Closures. Context-Objekte halten starke Referenzen auf Kontextvariablen, was verhindert, dass Kontextvariablen ordnungsgemäß vom Garbage Collector bereinigt werden.

name

Der Name der Variablen. Dies ist eine schreibgeschützte Eigenschaft.

Hinzugefügt in Version 3.7.1.

get([default])

Gibt einen Wert für die Kontextvariable für den aktuellen Kontext zurück.

Wenn kein Wert für die Variable im aktuellen Kontext vorhanden ist, gibt die Methode

  • den Wert des Arguments default der Methode zurück, falls es bereitgestellt wird; oder

  • gibt den Standardwert für die Kontextvariable zurück, wenn sie mit einem erstellt wurde; oder

  • löst einen LookupError aus.

set(value)

Aufruf zum Setzen eines neuen Wertes für die Kontextvariable im aktuellen Kontext.

Das erforderliche Argument value ist der neue Wert für die Kontextvariable.

Gibt ein Token-Objekt zurück, das verwendet werden kann, um die Variable mit der Methode ContextVar.reset() auf ihren vorherigen Wert zurückzusetzen.

reset(token)

Setzt die Kontextvariable auf den Wert zurück, den sie vor der Verwendung des ContextVar.set()-Aufrufs hatte, der das token erstellt hat.

Zum Beispiel

var = ContextVar('var')

token = var.set('new value')
# code that uses 'var'; var.get() returns 'new value'.
var.reset(token)

# After the reset call the var has no value again, so
# var.get() would raise a LookupError.
class contextvars.Token

Token-Objekte werden von der Methode ContextVar.set() zurückgegeben. Sie können an die Methode ContextVar.reset() übergeben werden, um den Wert der Variable auf den Zustand vor dem entsprechenden set zurückzusetzen.

Das Token unterstützt das Protokoll für Kontextmanager, um den Wert der entsprechenden Kontextvariable beim Verlassen eines with-Blocks wiederherzustellen.

var = ContextVar('var', default='default value')

with var.set('new value'):
    assert var.get() == 'new value'

assert var.get() == 'default value'

Hinzugefügt in Version 3.14: Unterstützung für die Verwendung als Kontextmanager hinzugefügt.

var

Eine schreibgeschützte Eigenschaft. Zeigt auf das ContextVar-Objekt, das das Token erstellt hat.

old_value

Eine schreibgeschützte Eigenschaft. Setzt sich auf den Wert, den die Variable vor dem Aufruf von ContextVar.set() hatte, der das Token erstellt hat. Sie zeigt auf Token.MISSING, wenn die Variable vor dem Aufruf nicht gesetzt war.

MISSING

Ein Markierungsobjekt, das von Token.old_value verwendet wird.

Manuelle Kontextverwaltung

contextvars.copy_context()

Gibt eine Kopie des aktuellen Context-Objekts zurück.

Das folgende Snippet holt eine Kopie des aktuellen Kontexts und gibt alle Variablen und ihre Werte aus, die darin gesetzt sind.

ctx: Context = copy_context()
print(list(ctx.items()))

Die Funktion hat eine Komplexität von O(1), d.h. sie arbeitet gleich schnell für Kontexte mit wenigen Kontextvariablen und für Kontexte, die viele davon haben.

class contextvars.Context

Eine Abbildung von ContextVars zu ihren Werten.

Context() erstellt einen leeren Kontext ohne Werte darin. Um eine Kopie des aktuellen Kontexts zu erhalten, verwenden Sie die Funktion copy_context().

Jeder Thread hat seinen eigenen effektiven Stapel von Context-Objekten. Der aktuelle Kontext ist das Context-Objekt oben auf dem Stapel des aktuellen Threads. Alle Context-Objekte in den Stapeln gelten als betreten.

Das Betreten eines Kontexts, was durch Aufrufen seiner Methode run() geschehen kann, macht den Kontext zum aktuellen Kontext, indem er auf den Stapel des aktuellen Threads geschoben wird.

Das Verlassen des aktuellen Kontexts, was durch Rückkehr aus dem Callback geschehen kann, der an die Methode run() übergeben wird, stellt den aktuellen Kontext auf seinen Zustand vor dem Betreten des Kontexts zurück, indem der Kontext vom oberen Ende des Kontextstapels entfernt wird.

Da jeder Thread seinen eigenen Kontextstapel hat, verhalten sich ContextVar-Objekte ähnlich wie threading.local(), wenn Werte in verschiedenen Threads zugewiesen werden.

Der Versuch, einen bereits betretenen Kontext zu betreten, einschließlich Kontexten, die in anderen Threads betreten wurden, löst einen RuntimeError aus.

Nach dem Verlassen eines Kontexts kann er später wieder betreten werden (von jedem Thread).

Alle Änderungen an ContextVar-Werten über die Methode ContextVar.set() werden im aktuellen Kontext aufgezeichnet. Die Methode ContextVar.get() gibt den Wert zurück, der dem aktuellen Kontext zugeordnet ist. Das Verlassen eines Kontexts macht effektiv alle Änderungen rückgängig, die an Kontextvariablen vorgenommen wurden, während der Kontext betreten war (falls erforderlich, können die Werte durch erneutes Betreten des Kontexts wiederhergestellt werden).

Context implementiert die collections.abc.Mapping-Schnittstelle.

run(callable, *args, **kwargs)

Betritt den Context, führt callable(*args, **kwargs) aus, und verlässt dann den Context. Gibt den Rückgabewert von callable zurück oder gibt eine Ausnahme weiter, falls eine aufgetreten ist.

Beispiel

import contextvars

var = contextvars.ContextVar('var')
var.set('spam')
print(var.get())  # 'spam'

ctx = contextvars.copy_context()

def main():
    # 'var' was set to 'spam' before
    # calling 'copy_context()' and 'ctx.run(main)', so:
    print(var.get())  # 'spam'
    print(ctx[var])  # 'spam'

    var.set('ham')

    # Now, after setting 'var' to 'ham':
    print(var.get())  # 'ham'
    print(ctx[var])  # 'ham'

# Any changes that the 'main' function makes to 'var'
# will be contained in 'ctx'.
ctx.run(main)

# The 'main()' function was run in the 'ctx' context,
# so changes to 'var' are contained in it:
print(ctx[var])  # 'ham'

# However, outside of 'ctx', 'var' is still set to 'spam':
print(var.get())  # 'spam'
copy()

Gibt eine flache Kopie des Kontextobjekts zurück.

var in context

Gibt True zurück, wenn der context einen Wert für var gesetzt hat; gibt andernfalls False zurück.

context[var]

Gibt den Wert der ContextVar-Variable var zurück. Wenn die Variable im Kontextobjekt nicht gesetzt ist, wird ein KeyError ausgelöst.

get(var[, default])

Gibt den Wert für var zurück, wenn var den Wert im Kontextobjekt hat. Gibt andernfalls default zurück. Wenn default nicht angegeben wird, wird None zurückgegeben.

iter(context)

Gibt einen Iterator über die im Kontextobjekt gespeicherten Variablen zurück.

len(proxy)

Gibt die Anzahl der im Kontextobjekt gesetzten Variablen zurück.

keys()

Gibt eine Liste aller Variablen im Kontextobjekt zurück.

values()

Gibt eine Liste aller Werte der Variablen im Kontextobjekt zurück.

items()

Gibt eine Liste von 2-Tupeln zurück, die alle Variablen und ihre Werte im Kontextobjekt enthalten.

asyncio-Unterstützung

Kontextvariablen werden nativ in asyncio unterstützt und können ohne zusätzliche Konfiguration verwendet werden. Zum Beispiel hier ein einfacher Echo-Server, der eine Kontextvariable verwendet, um die Adresse eines entfernten Clients im Task verfügbar zu machen, der diesen Client verarbeitet.

import asyncio
import contextvars

client_addr_var = contextvars.ContextVar('client_addr')

def render_goodbye():
    # The address of the currently handled client can be accessed
    # without passing it explicitly to this function.

    client_addr = client_addr_var.get()
    return f'Good bye, client @ {client_addr}\r\n'.encode()

async def handle_request(reader, writer):
    addr = writer.transport.get_extra_info('socket').getpeername()
    client_addr_var.set(addr)

    # In any code that we call is now possible to get
    # client's address by calling 'client_addr_var.get()'.

    while True:
        line = await reader.readline()
        print(line)
        if not line.strip():
            break

    writer.write(b'HTTP/1.1 200 OK\r\n')  # status line
    writer.write(b'\r\n')  # headers
    writer.write(render_goodbye())  # body
    writer.close()

async def main():
    srv = await asyncio.start_server(
        handle_request, '127.0.0.1', 8081)

    async with srv:
        await srv.serve_forever()

asyncio.run(main())

# To test it you can use telnet or curl:
#     telnet 127.0.0.1 8081
#     curl 127.0.0.1:8081