contextvars — Kontextvariabler


Denna modul tillhandahåller API:er för att hantera, lagra och komma åt kontextlokalt tillstånd. Klassen ContextVar används för att deklarera och arbeta med kontextvariabler. Funktionen copy_context() och klassen Context bör användas för att hantera den aktuella kontexten i asynkrona ramverk.

Kontexthanterare som har tillstånd bör använda kontextvariabler istället för threading.local() för att förhindra att deras tillstånd oväntat sprids till annan kod när de används i samtidig kod.

Se även PEP 567 för ytterligare information.

Tillagd i version 3.7.

Variabler för kontext

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

Denna klass används för att deklarera en ny kontextvariabel, t.ex.:

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

Den obligatoriska parametern name används för introspektion och felsökning.

Den valfria parametern default, som endast innehåller nyckelord, returneras av ContextVar.get() när inget värde för variabeln hittas i den aktuella kontexten.

Viktigt: Kontextvariabler ska skapas på den översta modulnivån och aldrig i avslutningar. Context-objekt innehåller starka referenser till kontextvariabler vilket förhindrar att kontextvariablerna samlas in på rätt sätt.

name

Namnet på variabeln. Detta är en skrivskyddad egenskap.

Tillagd i version 3.7.1.

get([default])

Returnerar ett värde för kontextvariabeln för den aktuella kontexten.

Om det inte finns något värde för variabeln i det aktuella sammanhanget kommer metoden att göra det:

  • returnera värdet för metodens standard-argument, om ett sådant har angetts; eller

  • returnera standardvärdet för kontextvariabeln, om den skapades med ett sådant; eller

  • skapa ett LookupError.

set(value)

Anrop för att ange ett nytt värde för kontextvariabeln i den aktuella kontexten.

Det obligatoriska argumentet value är det nya värdet för kontextvariabeln.

Returnerar ett Token-objekt som kan användas för att återställa variabeln till dess tidigare värde via metoden ContextVar.reset().

reset(token)

Återställer kontextvariabeln till det värde den hade innan ContextVar.set() som skapade token användes.

Till exempel:

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-objekt returneras av metoden ContextVar.set(). De kan skickas till metoden ContextVar.reset() för att återställa variabelns värde till vad det var före motsvarande set.

Token stöder context manager protocol för att återställa motsvarande kontextvariabelvärde vid utgången från with block:

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

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

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

Tillagd i version 3.14: Stöd för användning som kontexthanterare har lagts till.

var

En skrivskyddad egenskap. Pekar på det ContextVar-objekt som skapade token.

old_value

En skrivskyddad egenskap. Ställs in till det värde variabeln hade före metodanropet ContextVar.set() som skapade token. Den pekar på Token.MISSING om variabeln inte var inställd före anropet.

MISSING

Ett markörobjekt som används av Token.old_value.

Manuell kontexthantering

contextvars.copy_context()

Returnerar en kopia av det aktuella Context-objektet.

Följande utdrag hämtar en kopia av den aktuella kontexten och skriver ut alla variabler och deras värden som har angetts i den:

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

Funktionen har en O(1) komplexitet, dvs. den arbetar lika snabbt för kontexter med ett fåtal kontextvariabler som för kontexter med många.

class contextvars.Context

En mappning av ContextVars till deras värden.

Context() skapar en tom kontext utan några värden i den. För att få en kopia av den aktuella kontexten används funktionen copy_context().

Varje tråd har sin egen effektiva stack av Context-objekt. Den current context är det Context-objekt som ligger överst i den aktuella trådens stack. Alla Context-objekt i staplarna anses vara inmatade.

När man går in i en kontext, vilket kan göras genom att anropa dess run()-metod, blir kontexten den aktuella kontexten genom att den läggs överst i den aktuella trådens kontextstapel.

När du lämnar den aktuella kontexten, vilket kan göras genom att återvända från den återuppringning som skickades till metoden run(), återställs den aktuella kontexten till vad den var innan kontexten angavs genom att kontexten flyttas från toppen av kontextstapeln.

Eftersom varje tråd har sin egen kontextstack, beter sig ContextVar-objekt på ett liknande sätt som threading.local() när värden tilldelas i olika trådar.

Försök att ange en redan angiven kontext, inklusive kontexter som angetts i andra trådar, ger upphov till ett RuntimeError.

När du har lämnat ett sammanhang kan du senare gå in i det igen (från vilken tråd som helst).

Alla ändringar av ContextVar-värden via metoden ContextVar.set() registreras i den aktuella kontexten. Metoden ContextVar.get() returnerar det värde som är associerat med den aktuella kontexten. När en kontext avslutas återställs alla ändringar som gjorts i kontextvariablerna när kontexten öppnades (vid behov kan värdena återställas genom att kontexten öppnas på nytt).

Kontexten implementerar gränssnittet collections.abc.Mapping.

run(callable, *args, **kwargs)

Går in i kontexten, utför callable(*args, **kwargs) och går sedan ut ur kontexten. Returnerar callable:s returvärde, eller sprider ett undantag om ett sådant inträffat.

Exempel:

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()

Returnerar en ytlig kopia av kontextobjektet.

var in context

Returnerar True om kontexten har ett värde för var inställt; returnerar False annars.

context[var]

Returnerar värdet på variabeln var ContextVar. Om variabeln inte är inställd i context-objektet, uppstår ett KeyError.

get(var[, default])

Returnerar värdet för var om var har värdet i kontextobjektet. Returnerar annars default. Om default inte anges, returneras None.

iter(context)

Returnerar en iterator över de variabler som finns lagrade i kontextobjektet.

len(proxy)

Returnerar antalet variabler som ställts in i kontextobjektet.

keys()

Returnerar en lista över alla variabler i kontextobjektet.

values()

Returnerar en lista över alla variablers värden i kontextobjektet.

items()

Returnerar en lista med 2-tuples som innehåller alla variabler och deras värden i kontextobjektet.

stöd för asyncio

Kontextvariabler stöds inbyggt i asyncio och är redo att användas utan någon extra konfiguration. Här är till exempel en enkel ekoserver som använder en kontextvariabel för att göra adressen till en fjärrklient tillgänglig i den Task som hanterar den klienten:

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