2.24. Kontextuskezelők

A with utasítás lefuttat egy beállító kódot, majd egy törzset, majd egy lebontó kódot – azzal a garanciával, hogy a lebontás akkor is lefut, ha a törzs félúton hibára fut. A beállítást és a lebontást biztosító metóduspáros neve kontextuskezelő.

Az alakja:

with <expression> as <name>:
    <body>

A kifejezés egy kontextuskezelőt ad vissza. A Python meghívja annak __enter__ metódusát, opcionálisan hozzáköti az eredményt a <name> névhez, lefuttatja a törzset, majd meghívja az __exit__ metódust – akár normálisan fejeződött be a törzs, akár kivételt dobott.

Folyamatábra: először az __enter__ fut, majd a törzs; a törzs vagy normálisan befejeződik, vagy kivételt dob; akárhogy is, a végén az __exit__ fut le.

Az __exit__ a blokk végén lefut, bármi is történik benne.

2.24.1. Egy kontextuskezelő használata

A klasszikus példa egy fájl megnyitása:

with open("data.txt") as f:
    text = f.read()

# f is now closed, even if read() failed

Az open() egy fájlobjektumot ad vissza, amely maga is kontextuskezelő; az __enter__ visszaadja a fájlt, az __exit__ pedig bezárja azt. A with blokk az „a fájlt mindig zárd be, ha végeztél” megközelítést teszi alapértelmezetté ahelyett, hogy a hívónak kellene rá emlékeznie.

2.24.1.1. Több kontextuskezelő

Egyetlen with utasítás egyszerre több kontextuskezelőbe is beléphet, vesszővel elválasztva:

with open("input.txt") as src, open("output.txt", "w") as dst:
    dst.write(src.read())

Egyenértékű két egymásba ágyazott with blokkal, de laposabb. A kezelők balról jobbra lépnek be, és fordított sorrendben lépnek ki; ha a jobb oldali kezelő __enter__ metódusa kivételt dob, a bal oldali kezelő __exit__ metódusa akkor is lefut.

2.24.2. Egy kontextuskezelő megírása

Bármely olyan osztály, amelynek van __enter__ és __exit__ metódusa, kontextuskezelőként működik:

class Section:
    def __init__(self, label):
        self.label = label

    def __enter__(self):
        print("---", self.label, "---")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("--- end", self.label, "---")
        return False

with Section("setup"):
    print("doing the work")

Kimenet:

--- setup ---
doing the work
--- end setup ---

Az __exit__ három argumentumot kap, amelyek a blokkot lezáró kivételt írják le, vagy három None értéket, ha a blokk normálisan fejeződött be. A False (vagy None) visszaadása esetén a lebontás után bármely kivétel tovább terjedhet; a True visszaadása elnyelné azt.

Használj kontextuskezelőket bármely olyan erőforráshoz, amelynek „megnyit / bezár” vagy „lefoglal / felszabadít” életciklusa van – nem csak fájlokhoz. A minta a takarítást a beállítás mellett tartja ott, ahol mindkettő bevezetésre kerül, így egy hosszú függvény közepén elfelejtett bezárás nem szivárogtathatja el az erőforrást.