Salt la conținutul principal
Modulul 9 · Lecția 3 din 435/36 în curs~12 min
Lecții din modul (3/4)

Manager context: cu și __enter__/__exit__

Un manager de context este un obiect pe care îl utilizați cu with. Acesta garantează că anumite operațiuni de configurare/curățare au loc întotdeauna, chiar și atunci când apar erori. Exemplul canonic: deschiderea și închiderea unui fișier.

Modelul with

Python
with open("note.txt", "w") as f:
    f.write("ciao")
# qui f è già chiuso, anche se write() ha sollevato un'eccezione

În engleză simplă: „cu resursa X deschisă ca f, faceți asta. Când ați terminat (orice s-ar întâmpla), închide X." Fără with ar trebui să scrii un try/finally de fiecare dată.

Mai mult de unul la un moment dat

Python
with open("a.txt") as f, open("b.txt") as g:
    dati_a = f.read()
    dati_b = g.read()

Scrierea propriului manager de context (clasa)

Trebuie să definiți două metode dunder: __enter__ și __exit__.

Python
class Timer:
    def __enter__(self):
        import time
        self.t0 = time.perf_counter()
        return self  # è l'oggetto che finisce dopo "as"
    def __exit__(self, exc_type, exc_value, traceback):
        import time
        self.elapsed = time.perf_counter() - self.t0
        # ritornare True ingoia l'eccezione; di solito non lo vuoi
        return False

with Timer() as t:
    sum(range(10**6))
t.elapsed  # quanto è durato
  • __enter__(self) este apelat la intrarea în bloc; valoarea returnată este ceea ce obțineți după as.
  • __exit__(self, exc_type, exc_value, tb) este apelat la ieșire (întotdeauna). Dacă returnează True, orice excepție este suprimată.

Comanda rapidă: @contextmanager

contextlib.contextmanager vă permite să scrieți un manager de context ca a funcția generator:

Python
from contextlib import contextmanager

@contextmanager
def timer():
    import time
    t0 = time.perf_counter()
    try:
        yield  # qui sta il blocco "with"
    finally:
        elapsed = time.perf_counter() - t0
        print(f"durata: {elapsed:.4f}s")

with timer():
    sum(range(10**6))

Codul înainte yield este __enter__, codul după este __exit__. try/finally garantează curățarea chiar și atunci când apar erori.

suprima: ignora o excepție

Python
from contextlib import suppress

with suppress(FileNotFoundError):
    open("forse-non-esiste.txt").read()
# se il file non c'è, nessun errore propaga

Capcană: with numai pentru obiectele care îl suportă

Python
x = 42
with x:  # AttributeError: __enter__
    ...

Scrierea managerilor de context cu @contextmanager

Modulul contextlib al bibliotecii standard exportă un decorator extrem de convenabil numit @contextmanager pentru a crea manageri de context folosind funcții generatoare, în loc să scrie clase detaliate care implementează metodele __enter__ și __exit__. Doar scrieți o funcție generatoare care utilizează un singur yield:

Python
from contextlib import contextmanager

@contextmanager
def manage_resource():
    # __enter__ code
    yield resource
    # __exit__ code

Încercați-l singur

Exercițiu#python.m9.l3.e1
Încercări: 0Se încarcă…

Utilizați contextlib.suppress pentru a încerca int('non-numero') ignorând ValueError. După bloc, atribuiți `ok = True`. Evaluează `ok`.

Se încarcă editorul...
Afișează indiciu

suppress(ValueError) înghite numai ValueError.

Soluție disponibilă după 3 încercări

Exercițiu de revizuire

Exercițiu#python.m9.l3.e2
Încercări: 0Se încarcă…

Scrieți un manager de context `collect` cu @contextmanager care produce o listă goală și, la ieșire, o convertește într-un tuplu (printați-o). Folosiți-l pentru a adăuga 1, 2, 3 la lista din interiorul blocului. La sfârșit, evaluați `len(collection)` unde `collection` este lista rezultată.

Se încarcă editorul...
Afișează indiciu

elemente de randament pentru a expune lista din interiorul blocului with.

Soluție disponibilă după 3 încercări

Provocare suplimentară

Exercițiu#python.m9.l3.e3
Încercări: 0Se încarcă…

Write a custom context manager using the `@contextmanager` decorator from `contextlib`. The context manager must be named `mock_session`. Upon entry, it must append `'start'` to a global list `log_list = []`, yield control using `yield`, and upon exit append `'stop'`. Finally, evaluate `log_list` after using the context manager with `with mock_session():`.

Se încarcă editorul...
Afișează indiciu

Utilizați yield în interiorul mock_session pentru a suspenda execuția înainte de a adăuga „stop”.

Soluție disponibilă după 3 încercări