2.24. Gestionari de context

Instrucțiunea with rulează cod de configurare, apoi un corp, apoi cod de finalizare – cu garanția că finalizarea se execută chiar și atunci când corpul eșuează la jumătatea execuției. Perechea de metode care furnizează configurarea și finalizarea se numește gestionar de context.

Forma este:

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

Expresia returnează un gestionar de context. Python apelează metoda sa __enter__, leagă opțional rezultatul de <name>, rulează corpul, apoi apelează __exit__ – indiferent dacă corpul s-a încheiat normal sau a ridicat o excepție.

Diagramă de flux: __enter__ rulează prima, apoi corpul; corpul fie se încheie normal, fie ridică o excepție; în ambele cazuri, __exit__ rulează la final.

__exit__ rulează la sfârșitul blocului, indiferent de ce se întâmplă în interiorul acestuia.

2.24.1. Utilizarea unui gestionar de context

Exemplul canonic este deschiderea unui fișier:

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

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

open() returnează un obiect fișier care este el însuși un gestionar de context; __enter__ returnează fișierul, iar __exit__ îl închide. Blocul with face din „închide întotdeauna fișierul la final” comportamentul implicit, în loc de ceva pe care apelantul trebuie să-și amintească.

2.24.1.1. Mai mulți gestionari de context

O singură instrucțiune with poate intra în mai mulți gestionari de context deodată, separați prin virgule:

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

Echivalent cu două blocuri with imbricate, dar mai plat. Gestionarii sunt intrați de la stânga la dreapta și ieșiți în ordine inversă; dacă __enter__ de pe gestionarul din dreapta ridică o excepție, __exit__ al gestionarului din stânga se execută în continuare.

2.24.2. Scrierea unui gestionar de context

Orice clasă cu metodele __enter__ și __exit__ funcționează ca un gestionar de context:

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

Ieșire:

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

__exit__ primește trei argumente care descriu excepția ce a încheiat blocul, sau trei valori None dacă blocul s-a încheiat normal. Returnarea valorii False (sau None) lasă orice excepție să se propage după finalizare; returnarea valorii True ar înghiți-o.

Folosește gestionari de context pentru orice resursă care are un ciclu de viață de tip „deschidere / închidere” sau „achiziție / eliberare” – nu doar pentru fișiere. Tiparul păstrează curățarea împerecheată cu configurarea, în punctul în care ambele sunt introduse, astfel încât o închidere uitată la mijlocul unei funcții lungi nu poate scurge resursa.