Ačkoli původní ideou za zavedením kontextu with byla snaha naučit generátory vstupní a čisticí operace kolem bloku kódu (podívejte se do PEP'u 343), my se na něj podíváme nejdříve přesně z opačné strany – jako na nástroj šetřící try-finally u bloků kódu, které potřebují nějaké výstupní „čisticí“ operace.
Nebyl-li by kontext with k dispozici, vypadala by správná práce se soubory asi nějak takto:
Idea je taková, že dojde-li při práci se souborem k nějaké chybě, větev finally zajistí, že již otevřený soubor bude vždy řádně uzavřen.
V podání kontextu with vypadá stejný kus kódu takto:
Za prvé je to podstatně kratší. A za druhé je to nakonec i stejně výmluvné – jakmile si zafixujete, že kontext with se stará o „čištění“ za vás.
Samozřejmě automatické zavírání souborů je dosti omezená podmnožina toho, co kontext with umí – ostatně, jak už jsem říkal, byl navržen z poněkud jiných důvodů. Ve skutečnosti obstarává následující dvě funkce:
Mnoho objektů Python'u se uvedeným způsobem chová a je je tedy možno použít uvnitř kontextu with. Zároveň existuje několik způsobů, jak vyrobit vlastní objekty, které budou umožňovat to samé.
Nejnázornější je asi implementace pomocí třídy – objekt, který má podporovat spolupráci s kontextem with, musí definovat následující dvě metody:
__enter__()
– operace, které se mají vykonat před spuštěním kódu v BLOKu;
__exit__()
– operace, které se mají vykonat po opuštění kódu v BLOKu.
Přitom platí:
as
, je tato reference návratovou hodnotou z metody __enter__()
.
__exit__()
jako trojce typ, hodnota, traceback; v opačném případě metoda obdrží trojci None, None, None
.
__exit__()
vrátí False
, je tato výjimka předána výše; pokud ovšem ve stejném případě __exit__()
vrátí True
, je výjimka potlačena.
Z metody __enter__()
můžete vrátit skutečně cokoli, tak jen pro ukázku:
Vzhledem k tomu, že pro spolupráci s kontextem with jsou potřeba dva oddělené kusy kódu, nepřekvapí, že je možné proměnit generátor s jedním yield'em na odpovídající třídu – kód před yield
je promítnut do části __enter__()
, kód po něm pak do __exit__()
.
Na uvedený překlad je pochopitelně k dispozici standardní dekorátor @contextmanager
, jenž při nejjednodušším použití vypadá takto:
Poněkud komplikovanější použití kopírující chování předchozí třídní varianty:
Když nahlédneme do příslušného PEP'u, odpovídá práce se soubory v podstatě následujícímu kódu:
Tedy v rámci __enter__() je soubor otevřen a reference na něj je předána do kontextu a po skončení práce s ním je v rámci __exit__() soubor uzavřen.
Přestože pomocí generátoru i třídy lze kontext with připravit téměř úplně stejný, existují drobné rozdíly, které vám v konkrétním případě mohou upřednostnit jen jeden z nich:
Knihovna contextlib obsahuje mnohem více zajímavých (a složitých) pomocných metod – mimo jiné i kontexty, které je možno navštívit vícekrát než jednou v rámci stejného běhu.
Spousta běžných kontextů je ale „jednoběhová“ – chcete-li je použít znovu, musíte je znovu zavést (proto se také kontexty zpravidla vytváří přímo uvnitř výrazu with
). Příkladem jsou třebas soubory, které se po použití v kontextu uzavřou, což si můžeme nasimulovat třebas takto:
with můj_kontext():
by samozřejmě fungovala při každém volání právě proto, že uvedený „jednoběhový“ kontext je pokaždé znovu vytvořen.
Poznamenejme ještě, že kód..
..je ekvivalentní kódu:
A hlavně – od Python'u 3.10 konečně půjde uzávorkovat! Takže bude možné psát třebas: