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__()
ochobject.__exit__()
. En standard implementation förobject.__enter__()
tillhandahålls som returnerarself
medanobject.__exit__()
är en abstrakt metod som som standard returnerarNone
. 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__()
ochobject.__aexit__()
. En standard implementation förobject.__aenter__()
tillhandahålls som returnerarself
medanobject.__aexit__()
är en abstrakt metod som som standard returnerarNone
. 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 medcontextlib.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
-satsensas
-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 entry
…except
…finally
-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örwith
-satsen att undantaget har hanterats, och exekveringen kommer att återupptas med den sats som följer omedelbart efterwith
-satsen.contextmanager()
använderContextDecorator
så att de kontexthanterare som skapas kan användas som dekoratorer såväl som iwith
-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 avcontextmanager()
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 medasync 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 kommerpage.close()
att anropas närwith
-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 ärclosing()
mest användbar för tredjepartstyper som inte stöder kontexthanterare. Detta exempel är enbart för illustrationsändamål, eftersomurlopen()
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 genombreak
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 avwith
-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 enBaseExceptionGroup
, 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 gruppensderive()
-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 enBaseExceptionGroup
.
- 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 ettio.StringIO
-objekt. Ersättningsströmmen returneras från metoden__enter__
och är därför tillgänglig som mål förwith
-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 omdirigerarsys.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 avcontextmanager()
, 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 explicitawith
-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__()
returnerarExitStack
-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örAttributeError
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 enwith
-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örAttributeError
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.
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