contextlib — Verktyg för with -satskontexter

Källkod: Lib/contextlib.py


Denna modul tillhandahåller verktyg för vanliga uppgifter som involverar with-satsen. För mer information se även Typer av kontexthanterare och Med uttalande från kontextansvariga.

Verktyg

Funktioner och klasser som tillhandahålls:

class contextlib.AbstractContextManager

En abstrakt basklass för klasser som implementerar object.__enter__() och object.__exit__(). En standard implementation för object.__enter__() tillhandahålls som returnerar self medan object.__exit__() är en abstrakt metod som som standard returnerar None. Se även definitionen av Typer av kontexthanterare.

Tillagd i version 3.6.

class contextlib.AbstractAsyncContextManager

En abstrakt basklass för klasser som implementerar object.__aenter__() och object.__aexit__(). En standard implementation för object.__aenter__() tillhandahålls som returnerar self medan object.__aexit__() är en abstrakt metod som som standard returnerar None. Se även definitionen av Asynkrona kontexthanterare.

Tillagd i version 3.7.

@contextlib.contextmanager

Denna funktion är en decorator som kan användas för att definiera en fabriksfunktion för with statement context managers, utan att behöva skapa en klass eller separata __enter__() och __exit__() metoder.

Även om många objekt har inbyggt stöd för användning i with-satser, kan det ibland vara nödvändigt att hantera en resurs som inte är en kontextmanager i sig och som inte implementerar en close() -metod för användning med contextlib.closing.

Ett abstrakt exempel skulle kunna vara följande för att säkerställa korrekt resurshantering:

från contextlib import contextmanager

@kontexthanterare
def managed_resource(*args, **kwds):
    # Kod för att förvärva resurs, t.ex:
    resurs = förvärva_resurs(*args, **kwds)
    försök:
        ge resurs
    slutligen:
        # Kod för att frigöra en resurs, t.ex:
        release_resource(resurs)

Funktionen kan sedan användas på följande sätt:

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

Den funktion som dekoreras måste returnera en generator-iterator när den anropas. Denna iterator måste ge exakt ett värde, som kommer att bindas till målen i with-satsens as-klausul, om någon.

Vid den punkt där generatorn ger upp, körs det block som är inbäddat i with-satsen. Generatorn återupptas sedan efter att blocket har avslutats. Om ett ohanterat undantag inträffar i blocket, återupprepas det inuti generatorn vid den punkt där yield inträffade. Du kan alltså använda en tryexceptfinally-sats för att fånga upp felet (om det finns något) eller se till att en viss upprensning sker. Om ett undantag fångas enbart för att logga det eller för att utföra någon åtgärd (snarare än att undertrycka det helt), måste generatorn göra om undantaget. I annat fall kommer generatorns kontexthanterare att indikera för with-satsen att undantaget har hanterats, och exekveringen kommer att återupptas med den sats som följer omedelbart efter with-satsen.

contextmanager() använder ContextDecorator så att de kontexthanterare som skapas kan användas som dekoratorer såväl som i with-satser. När den används som en dekorator skapas en ny generatorinstans implicit vid varje funktionsanrop (detta gör att de annars ”enstaka” kontexthanterare som skapas av contextmanager() uppfyller kravet på att kontexthanterare stöder flera anrop för att kunna användas som dekoratorer).

Ändrad i version 3.2: Användning av ContextDecorator.

@contextlib.asynccontextmanager

Liknar contextmanager(), men skapar en asynkron kontexthanterare.

Denna funktion är en decorator som kan användas för att definiera en fabriksfunktion för async with statement asynchronous context managers, utan att behöva skapa en klass eller separata __aenter__() och __aexit__() metoder. Den måste tillämpas på en asynchronous generator-funktion.

Ett enkelt exempel:

from contextlib import asynccontextmanager

@asynccontexthanterare
async def get_connection():
    conn = await acquire_db_connection()
    try:
        ge conn
    slutligen:
        await release_db_connection(conn)

async def get_all_users():
    async med get_connection() som conn:
        return conn.query('SELECT ...')

Tillagd i version 3.7.

Kontexthanterare som definieras med asynccontextmanager() kan användas antingen som dekoratorer eller med async with-satser:

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

När den används som en dekorator skapas en ny generatorinstans implicit vid varje funktionsanrop. Detta gör att de annars ”one-shot” kontexthanterare som skapas av asynccontextmanager() uppfyller kravet på att kontexthanterare stöder flera anrop för att kunna användas som dekoratorer.

Ändrad i version 3.10: Asynkrona kontexthanterare som skapats med asynccontextmanager() kan användas som dekoratorer.

contextlib.closing(thing)

Returnerar en kontexthanterare som stänger något när blocket har slutförts. Detta är i princip likvärdigt med:

från contextlib import contextmanager

@kontexthanterare
def stängning(sak):
    försök:
        ge sak
    slutligen:
        thing.close()

Och låter dig skriva kod så här:

from contextlib import closing
from urllib.request import urlopen

with closing(urlopen('https://www.python.org')) as page:
    for line in page:
        print(line)

utan att uttryckligen behöva stänga page. Även om ett fel inträffar kommer page.close() att anropas när with-blocket avslutas.

Anteckning

De flesta typer som hanterar resurser stöder context manager-protokollet, som stänger ting när det lämnar with-satsen. Som sådan är closing() mest användbar för tredjepartstyper som inte stöder kontexthanterare. Detta exempel är enbart för illustrationsändamål, eftersom urlopen() normalt skulle användas i en kontexthanterare.

contextlib.aclosing(thing)

Returnerar en asynkron kontexthanterare som anropar aclose()-metoden för ting när blocket har slutförts. Detta är i princip likvärdigt med:

from contextlib import asynccontextmanager

@asynccontexthanterare
async def aclosing(sak):
    try:
        avkastning sak
    slutligen:
        await thing.aclose()

Det är viktigt att aclosing() stöder deterministisk rensning av async-generatorer när de råkar avslutas tidigt genom break eller ett undantag. Till exempel:

from contextlib import aclosing

async med aclosing(my_generator()) som värden:
    async för värde i värden:
        if värde == 42:
            break

Detta mönster säkerställer att generatorns asynkrona exitkod exekveras i samma kontext som dess iterationer (så att undantag och kontextvariabler fungerar som förväntat och exitkoden inte körs efter livstiden för någon uppgift som den är beroende av).

Tillagd i version 3.10.

contextlib.nullcontext(enter_result=None)

Returnerar en kontexthanterare som returnerar enter_result från __enter__, men som i övrigt inte gör någonting. Den är avsedd att användas som en ersättare för en valfri kontexthanterare, till exempel:

def myfunction(arg, ignore_exceptions=False):
    if ignore_exceptions:
        # Använd suppress för att ignorera alla undantag.
        cm = contextlib.suppress(Undantag)
    else:
        # Ignorera inte några undantag, cm har ingen effekt.
        cm = contextlib.nullcontext()
    med cm:
        # Gör något

Ett exempel på användning av enter_result:

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

Det kan också användas som en ersättare för asynkrona kontexthanterare:

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

Tillagd i version 3.7.

Ändrad i version 3.10: Stöd för asynchronous context manager har lagts till.

contextlib.suppress(*exceptions)

Returnerar en kontexthanterare som undertrycker något av de angivna undantagen om de förekommer i en with-sats och sedan återupptar exekveringen med den första satsen efter slutet av with-satsen.

Som med alla andra mekanismer som helt undertrycker undantag, bör denna kontexthanterare endast användas för att täcka mycket specifika fel där det är känt att det är rätt att fortsätta att exekvera programmet i tysthet.

Till exempel:

from contextlib import suppress

with suppress(FileNotFoundError):
    os.remove('somefile.tmp')

with suppress(FileNotFoundError):
    os.remove('someotherfile.tmp')

Denna kod är likvärdig med:

try:
    os.remove('somefile.tmp')
except FileNotFoundError:
    pass

try:
    os.remove('someotherfile.tmp')
except FileNotFoundError:
    pass

Denna kontexthanterare är reentrant.

Om koden inom with-blocket ger upphov till en BaseExceptionGroup, tas undertryckta undantag bort från gruppen. Alla undantag i gruppen som inte undertryckts tas upp igen i en ny grupp som skapas med hjälp av den ursprungliga gruppens derive()-metod.

Tillagd i version 3.4.

Ändrad i version 3.12: suppress stöder nu undertryckande av undantag som tagits upp som en del av en BaseExceptionGroup.

contextlib.redirect_stdout(new_target)

Kontexthanterare för tillfällig omdirigering av sys.stdout till en annan fil eller ett annat filliknande objekt.

Detta verktyg ger flexibilitet till befintliga funktioner eller klasser vars utdata är kopplad till stdout.

Till exempel skickas utdata från help() normalt till sys.stdout. Du kan fånga utdata i en sträng genom att omdirigera utdata till ett io.StringIO-objekt. Ersättningsströmmen returneras från metoden __enter__ och är därför tillgänglig som mål för with-satsen:

with redirect_stdout(io.StringIO()) as f:
    help(pow)
s = f.getvalue()

För att skicka utdata från help() till en fil på disken, omdirigera utdata till en vanlig fil:

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

För att skicka utdata från help() till sys.stderr:

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

Observera att den globala bieffekten på sys.stdout innebär att denna kontexthanterare inte är lämplig att använda i bibliotekskod och de flesta trådade applikationer. Den har inte heller någon effekt på utdata från underprocesser. Det är dock fortfarande ett användbart tillvägagångssätt för många verktygsskript.

Denna kontexthanterare är reentrant.

Tillagd i version 3.4.

contextlib.redirect_stderr(new_target)

Liknar redirect_stdout() men omdirigerar sys.stderr till en annan fil eller ett annat filliknande objekt.

Denna kontexthanterare är reentrant.

Tillagd i version 3.5.

contextlib.chdir(path)

Icke parallell-säker kontexthanterare för att ändra den aktuella arbetskatalogen. Eftersom detta ändrar ett globalt tillstånd, arbetskatalogen, är det inte lämpligt att använda i de flesta trådade eller asynkrona sammanhang. Det är inte heller lämpligt för de flesta icke-linjära kodkörningar, som generatorer, där programkörningen tillfälligt överlåts - om det inte uttryckligen önskas bör du inte överlåta när denna kontexthanterare är aktiv.

Detta är ett enkelt omslag runt chdir(), det ändrar den aktuella arbetskatalogen vid inmatning och återställer den gamla vid utmatning.

Denna kontexthanterare är reentrant.

Tillagd i version 3.11.

class contextlib.ContextDecorator

En basklass som gör att en kontexthanterare också kan användas som dekorator.

Kontexthanterare som ärver från ContextDecorator måste implementera __enter__ och __exit__ som vanligt. __exit__ behåller sin valfria undantagshantering även när den används som en dekorator.

ContextDecorator används av contextmanager(), så du får denna funktionalitet automatiskt.

Exempel på ContextDecorator:

from contextlib import ContextDecorator

class mycontext(ContextDecorator):
    def __enter__(self):
        print('Starting')
        return self

    def __exit__(self, *exc):
        print('Finishing')
        return False

Klassen kan sedan användas på följande sätt:

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

Denna ändring är bara syntaktiskt socker för alla konstruktioner av följande form:

def f():
    with cm():
        # Gör saker

ContextDecorator låter dig istället skriva:

@cm()
def f():
    # Gör saker

Det gör det tydligt att cm gäller hela funktionen, snarare än bara en del av den (och att spara en indragningsnivå är också trevligt).

Befintliga kontexthanterare som redan har en basklass kan utökas genom att använda ContextDecorator som en mixin-klass:

från contextlib import ContextDecorator

klass mycontext(ContextBaseClass, ContextDecorator):
    def __enter__(self):
        return self

    def __exit__(self, *exc):
        return False

Anteckning

Eftersom den dekorerade funktionen måste kunna anropas flera gånger måste den underliggande kontexthanteraren stödja användning i flera with-satser. Om så inte är fallet ska den ursprungliga konstruktionen med den explicita with-satsen inuti funktionen användas.

Tillagd i version 3.2.

class contextlib.AsyncContextDecorator

Liknar ContextDecorator men endast för asynkrona funktioner.

Exempel på 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

Klassen kan sedan användas på följande sätt:

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

Tillagd i version 3.10.

class contextlib.ExitStack

En kontexthanterare som är utformad för att göra det enkelt att programmatiskt kombinera andra kontexthanterare och rensningsfunktioner, särskilt sådana som är valfria eller på annat sätt styrs av indata.

En uppsättning filer kan t.ex. enkelt hanteras i en enda with-sats enligt följande:

med ExitStack() som stack:
    files = [stack.enter_context(open(fname)) for fname in filnamn]
    # Alla öppnade filer kommer automatiskt att stängas i slutet av
    # with-satsen, även om försök att öppna filer senare i listan
    # i listan ger upphov till ett undantag

Metoden __enter__() returnerar ExitStack-instansen och utför inga ytterligare åtgärder.

Varje instans upprätthåller en stack med registrerade callbacks som anropas i omvänd ordning när instansen stängs (antingen explicit eller implicit i slutet av en with-sats). Observera att återanrop inte anropas implicit när context stack-instansen garbage collected.

Denna stackmodell används för att kontexthanterare som förvärvar sina resurser i metoden __init__ (t.ex. filobjekt) ska kunna hanteras korrekt.

Eftersom registrerade återuppringningar anropas i omvänd ordning efter registreringen, fungerar det som om flera nästlade with-satser hade använts med den registrerade uppsättningen återuppringningar. Detta gäller även undantagshantering - om en inre anropssignal undertrycker eller ersätter ett undantag, kommer yttre anropssignaler att få argument baserade på det uppdaterade tillståndet.

Detta är ett API på relativt låg nivå som tar hand om detaljerna kring korrekt avrullning av stacken med exit callbacks. Det utgör en lämplig grund för kontexthanterare på högre nivå som manipulerar exitstacken på applikationsspecifika sätt.

Tillagd i version 3.3.

enter_context(cm)

Ansluter en ny kontexthanterare och lägger till dess __exit__()-metod i callback-stacken. Returvärdet är resultatet av kontexthanterarens egen metod __enter__().

Dessa kontexthanterare kan undertrycka undantag precis som de normalt skulle göra om de användes direkt som en del av en with-sats.

Ändrad i version 3.11: Utlöser TypeError istället för AttributeError om cm inte är en kontexthanterare.

push(exit)

Lägger till en kontexthanterares __exit__()-metod till återkallelsestacken.

Eftersom __enter__ inte åberopas kan denna metod användas för att täcka en del av en __enter__()-implementering med en kontexthanterares egen __exit__()-metod.

Om ett objekt som inte är en kontexthanterare skickas, antar metoden att det är en återuppringning med samma signatur som en kontexthanterares __exit__()-metod och lägger till den direkt i återuppringningsstacken.

Genom att returnera sanna värden kan dessa callbacks undertrycka undantag på samma sätt som context manager __exit__()-metoder kan.

Det objekt som skickas in returneras från funktionen, vilket gör att denna metod kan användas som en funktionsdekorator.

callback(callback, /, *args, **kwds)

Accepterar en godtycklig återuppringningsfunktion och argument och lägger till den i återuppringningsstacken.

Till skillnad från de andra metoderna kan callbacks som läggs till på det här sättet inte undertrycka undantag (eftersom de aldrig får information om undantaget).

Den överlämnade återkallelsen returneras från funktionen, vilket gör att den här metoden kan användas som en funktionsdekorator.

pop_all()

Överför återkallelsestacken till en ny ExitStack-instans och returnerar den. Inga återuppringningar anropas av denna operation - istället kommer de nu att anropas när den nya stacken stängs (antingen explicit eller implicit i slutet av en with-sats).

En grupp filer kan t.ex. öppnas som en ”allt eller inget”-operation på följande sätt:

med ExitStack() som stack:
    files = [stack.enter_context(open(fname)) for fname in filnamn]
    # Håll fast vid close-metoden, men anropa den inte än.
    close_files = stack.pop_all().close
    # Om det inte går att öppna någon fil kommer alla tidigare öppnade filer att
    # stängas automatiskt. Om alla filer öppnas framgångsrikt,
    # kommer de att förbli öppna även efter att with-satsen har avslutats.
    # close_files() kan då åberopas explicit för att stänga dem alla.
close()

Avvecklar omedelbart återkallelsestacken och anropar återkallelser i omvänd registreringsordning. För alla kontexthanterare och exit callbacks som registrerats, kommer de argument som skickas in att ange att inget undantag inträffade.

class contextlib.AsyncExitStack

En asynkron kontexthanterare, liknande ExitStack, som stöder kombination av både synkrona och asynkrona kontexthanterare, samt har coroutines för rensningslogik.

Metoden close() är inte implementerad; aclose() måste användas istället.

async enter_async_context(cm)

Liknar ExitStack.enter_context() men förväntar sig en asynkron kontexthanterare.

Ändrad i version 3.11: Utlöser TypeError istället för AttributeError om cm inte är en asynkron kontexthanterare.

push_async_exit(exit)

Liknar ExitStack.push() men förväntar sig antingen en asynkron kontexthanterare eller en coroutine-funktion.

push_async_callback(callback, /, *args, **kwds)

Liknar ExitStack.callback() men förväntar sig en coroutine-funktion.

async aclose()

Liknar ExitStack.close() men hanterar awaitables korrekt.

Fortsätter exemplet för asynccontextmanager():

async med AsyncExitStack() som stack:
    anslutningar = [await stack.enter_async_context(get_connection())
        för i i intervall(5)]
    # Alla öppnade anslutningar kommer automatiskt att släppas i slutet av
    # async with-satsen, även om försök att öppna en anslutning senare i listan
    # senare i listan ger upphov till ett undantag.

Tillagd i version 3.7.

Exempel och recept

I det här avsnittet beskrivs några exempel och recept på hur du effektivt kan använda de verktyg som tillhandahålls av contextlib.

Stöd för ett varierande antal kontextansvariga

Det primära användningsområdet för ExitStack är det som anges i klassdokumentationen: att stödja ett varierande antal kontexthanterare och andra rensningsoperationer i en enda with-sats. Variationen kan bero på att antalet kontexthanterare som behövs styrs av användarens indata (t.ex. att öppna en användardefinierad samling filer) eller att vissa av kontexthanterarna är valfria:

med ExitStack() som stack:
    för resurs i resurser:
        stack.enter_context(resurs)
    if need_special_resource():
        special = förvärva_special_resurs()
        stack.callback(release_special_resource, special)
    # Utför operationer som använder de förvärvade resurserna

Som framgår gör ExitStack det också ganska enkelt att använda with-satser för att hantera godtyckliga resurser som inte har inbyggt stöd för kontexthanteringsprotokollet.

Fånga upp undantag från __enter__-metoder

Det är ibland önskvärt att fånga upp undantag från en metodimplementation av __enter__, utan att oavsiktligt fånga upp undantag från with-satsens kropp eller kontexthanterarens __exit__-metod. Genom att använda ExitStack kan stegen i protokollet för kontexthantering separeras något för att möjliggöra detta:

stack = ExitStack()
försök:
    x = stack.enter_context(cm)
except Exception:
    # hantera __enter__ undantag
else:
    med stack:
        # Hantera normalfallet

Att faktiskt behöva göra det här tyder sannolikt på att det underliggande API:et borde tillhandahålla ett direkt resurshanteringsgränssnitt för användning med try/except/finally-satser, men inte alla API:er är väl utformade i det avseendet. När en kontexthanterare är det enda API för resurshantering som tillhandahålls kan ExitStack göra det lättare att hantera olika situationer som inte kan hanteras direkt i en with-sats.

Städa upp i en __enter__-implementering

Som framgår av dokumentationen av ExitStack.push() kan den här metoden vara användbar för att rensa upp en redan tilldelad resurs om senare steg i __enter__()-implementeringen misslyckas.

Här är ett exempel på hur man gör detta för en kontexthanterare som accepterar funktioner för resursförvärv och frigöring, tillsammans med en valfri valideringsfunktion, och mappar dem till kontexthanteringsprotokollet:

from contextlib import contextmanager, AbstractContextManager, ExitStack

klass ResourceManager(AbstractContextManager):

    def __init__(self, acquire_resource, release_resource, check_resource_ok=None):
        self.acquire_resource = förvärva_resurs
        self.release_resource = release_resource
        om check_resource_ok är None:
            def check_resource_ok(resurs):
                return True
        self.check_resource_ok = check_resource_ok

    @kontexthanterare
    def _cleanup_on_error(self):
        med ExitStack() som stack:
            stack.push(self)
            avkastning
            # Valideringskontrollen godkändes och ledde inte till något undantag
            # Följaktligen vill vi behålla resursen och skicka den
            # tillbaka till vår anropare
            stack.pop_all()

    def __enter__(self):
        resurs = self.acquire_resource()
        med self._cleanup_on_error():
            if not self.check_resource_ok(resource):
                msg = "Misslyckades med validering för {!r}"
                raise RuntimeError(msg.format(resurs))
        returnera resurs

    def __exit__(self, *exc_details):
        # Vi behöver inte duplicera någon av våra logiker för resursfrisättning
        self.release_resource()

Ersätter all användning av try-finally och flaggvariabler

Ett mönster som du ibland kommer att se är ett try-finally-uttryck med en flaggvariabel som anger om kroppen i finally-satsen ska exekveras eller inte. I sin enklaste form (som inte kan hanteras genom att istället använda en except-sats) ser det ungefär så här ut:

cleanup_needed = True
försök:
    resultat = utföra_operation()
    om resultat:
        cleanup_needed = False
slutligen
    if cleanup_need:
        cleanup_resources()

Som med all kod som bygger på try-satser kan detta orsaka problem vid utveckling och granskning, eftersom installationskoden och upprensningskoden kan komma att separeras av godtyckligt långa kodavsnitt.

ExitStack gör det möjligt att istället registrera en callback för exekvering i slutet av en with-sats, och sedan senare bestämma sig för att hoppa över att exekvera den callbacken:

from contextlib import ExitStack

med ExitStack() som stack:
    stack.callback(cleanup_resources)
    resultat = utföra_operation()
    om resultat:
        stack.pop_all()

Detta gör att det avsedda uppstädningsbeteendet kan göras tydligt redan från början, i stället för att kräva en separat flaggvariabel.

Om en viss applikation använder detta mönster ofta kan det förenklas ytterligare med hjälp av en liten hjälpklass:

from contextlib import ExitStack

klass Återkallelse(ExitStack):
    def __init__(self, callback, /, *args, **kwds):
        super().__init__()
        self.callback(återuppringning, *args, **kwds)

    def cancel(self):
        self.pop_all()

med Callback(cleanup_resources) som cb:
    resultat = utföra_operation()
    om resultat:
        cb.cancel()

Om resursrensningen inte redan har paketerats i en fristående funktion är det fortfarande möjligt att använda dekoratorformen av ExitStack.callback() för att deklarera resursrensningen i förväg:

from contextlib import ExitStack

med ExitStack() som stack:
    @stack.återuppringning
    def cleanup_resources():
        ...
    resultat = utföra_operation()
    om resultat:
        stack.pop_all()

På grund av hur dekoratorprotokollet fungerar kan en callback-funktion som deklareras på detta sätt inte ta emot några parametrar. Istället måste alla resurser som ska frigöras nås som stängningsvariabler.

Använda en kontexthanterare som funktionsdekorator

ContextDecorator gör det möjligt att använda en kontexthanterare både i en vanlig with-sats och som en funktionsdekorator.

Till exempel är det ibland användbart att paketera funktioner eller grupper av satser med en logger som kan spåra tidpunkten för inmatning och tidpunkten för avslutning. I stället för att skriva både en funktionsdekorator och en kontexthanterare för uppgiften, ger ett arv från ContextDecorator båda funktionerna i en enda 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)

Instanser av denna klass kan användas som både en kontexthanterare:

med track_entry_and_exit('widget loader'):
    print('En tidskrävande aktivitet sker här')
    ladda_widget()

Och även som funktionsinredare:

@track_entry_and_exit('widgetladdare')
def aktivitet():
    print('Någon tidskrävande aktivitet sker här')
    ladda_widget()

Observera att det finns ytterligare en begränsning när man använder kontexthanterare som funktionsdekoratorer: det finns inget sätt att komma åt returvärdet för __enter__(). Om det värdet behövs är det fortfarande nödvändigt att använda en explicit with-sats.

Se även

PEP 343 - Statement “with”

Specifikation, bakgrund och exempel för Pythons with-sats.

Kontexthanterare för engångsbruk, återanvändbara och återcentrerade

De flesta kontexthanterare är skrivna på ett sätt som innebär att de bara kan användas effektivt i en with-sats en gång. Dessa kontexthanterare för engångsbruk måste skapas på nytt varje gång de används - om du försöker använda dem en andra gång kommer de att utlösa ett undantag eller på annat sätt inte fungera korrekt.

Denna vanliga begränsning innebär att det i allmänhet är tillrådligt att skapa kontexthanterare direkt i rubriken till with-satsen där de används (som visas i alla användningsexempel ovan).

Filer är ett exempel på kontexthanterare med effektiv engångsanvändning, eftersom den första with-satsen stänger filen och förhindrar ytterligare IO-operationer med det filobjektet.

Kontexthanterare som skapas med contextmanager() är också kontexthanterare för engångsbruk och kommer att klaga på att den underliggande generatorn inte ger något om man försöker använda dem en andra gång:

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

Reentranta kontexthanterare

Mer sofistikerade kontexthanterare kan vara ”reentranta”. Dessa kontexthanterare kan inte bara användas i flera with-satser, utan kan också användas inom en with-sats som redan använder samma kontexthanterare.

threading.RLock är ett exempel på en reentrant kontexthanterare, liksom suppress(), redirect_stdout() och chdir(). Här är ett mycket enkelt exempel på reentrant användning:

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

Verkliga exempel på reentrancy är mer benägna att involvera flera funktioner som anropar varandra och är därför mycket mer komplicerade än det här exemplet.

Observera också att reentrant inte är samma sak som trådsäkert. redirect_stdout() är till exempel definitivt inte trådsäkert, eftersom det gör en global ändring av systemtillståndet genom att binda sys.stdout till en annan ström.

Återanvändbara kontexthanterare

Till skillnad från både engångs- och återcentrerande kontexthanterare finns det ”återanvändbara” kontexthanterare (eller, för att vara helt tydlig, ”återanvändbara, men inte återcentrerande” kontexthanterare, eftersom återcentrerande kontexthanterare också är återanvändbara). Dessa kontexthanterare kan användas flera gånger, men kommer att misslyckas (eller på annat sätt inte fungera korrekt) om den specifika kontexthanteraren redan har använts i en containing with-sats.

threading.Lock är ett exempel på en återanvändbar, men inte reentrant, kontexthanterare (för ett reentrant lås är det nödvändigt att använda threading.RLock istället).

Ett annat exempel på en återanvändbar, men inte reentrant, kontexthanterare är ExitStack, eftersom den anropar alla för närvarande registrerade anrop när man lämnar en with-sats, oavsett var dessa anrop lades till:

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

Som framgår av exemplet fungerar det korrekt att återanvända ett enda stackobjekt i flera with-satser, men om man försöker nesta dem kommer stacken att rensas i slutet av den innersta with-satsen, vilket sannolikt inte är ett önskvärt beteende.

Genom att använda separata ExitStack-instanser istället för att återanvända en enda instans undviker man det problemet:

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