2.24. Kontekstinhallitsijat

with-lause suorittaa alustuskoodin, sitten rungon ja sitten purkukoodin – takeena, että purku suoritetaan, vaikka runko epäonnistuisi kesken kaiken. Metodiparia, joka tarjoaa alustuksen ja purun, kutsutaan kontekstinhallitsijaksi.

Muoto on:

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

Lauseke palauttaa kontekstinhallitsijan. Python kutsuu sen __enter__-metodia, sitoo valinnaisesti tuloksen nimeen <name>, suorittaa rungon ja kutsuu sitten __exit__-metodia – riippumatta siitä, valmistuiko runko normaalisti vai nostiko se poikkeuksen.

Vuokaavio: __enter__ suoritetaan ensin, sitten runko; runko joko valmistuu normaalisti tai nostaa poikkeuksen; kummassakin tapauksessa __exit__ suoritetaan lopuksi.

__exit__ suoritetaan lohkon lopussa riippumatta siitä, mitä sen sisällä tapahtuu.

2.24.1. Kontekstinhallitsijan käyttäminen

Kanoninen esimerkki on tiedoston avaaminen:

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

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

open() palauttaa tiedosto-olion, joka on itse kontekstinhallitsija; __enter__ palauttaa tiedoston ja __exit__ sulkee sen. with-lohko tekee ”sulje tiedosto aina, kun olet valmis” -toiminnasta oletusarvon sen sijaan, että kutsujan pitäisi muistaa se.

2.24.1.1. Useita kontekstinhallitsijoita

Yksittäinen with-lause voi siirtyä useaan kontekstinhallitsijaan kerralla, pilkuilla eroteltuina:

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

Vastaa kahta sisäkkäistä with-lohkoa, mutta on litteämpi. Hallitsijoihin siirrytään vasemmalta oikealle ja niistä poistutaan käänteisessä järjestyksessä; jos oikeanpuoleisen hallitsijan __enter__ nostaa poikkeuksen, vasemmanpuoleisen hallitsijan __exit__ suoritetaan silti.

2.24.2. Kontekstinhallitsijan kirjoittaminen

Mikä tahansa luokka, jolla on __enter__- ja __exit__-metodit, toimii kontekstinhallitsijana:

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

Tuloste:

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

__exit__ saa kolme argumenttia, jotka kuvaavat lohkon päättäneen poikkeuksen, tai kolme None-arvoa, jos lohko valmistui normaalisti. Arvon False (tai None) palauttaminen antaa minkä tahansa poikkeuksen edetä purkamisen jälkeen; arvon True palauttaminen nielaisisi sen.

Käytä kontekstinhallitsijoita mihin tahansa resurssiin, jolla on ”avaa / sulje”- tai ”varaa / vapauta” -elinkaari – ei vain tiedostoihin. Malli pitää siivouksen pariutettuna alustuksen kanssa siinä kohdassa, jossa molemmat esitellään, joten unohtunut sulkeminen keskellä pitkää funktiota ei voi vuotaa resurssia.