programowanieProgramista Backend Python

Jak działa menedżer kontekstu w Pythonie, po co jest potrzebny i jak zrealizować własny za pomocą protokołów __enter__ i __exit__? Jakie niuanse warto uwzględnić?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Menedżer kontekstu to obiekt, który definiuje zachowanie wejścia i wyjścia z bloku with, zapewniając automatyczne zarządzanie zasobami (plikami, połączeniami itp.). Jest realizowany za pomocą metod __enter__ i __exit__ w klasie lub przez dekorator @contextmanager z modułu contextlib.

Przykład realizacji:

class FileManager: def __init__(self, nazwa_pliku, tryb): self.nazwa_pliku = nazwa_pliku self.tryb = tryb self.plik = None def __enter__(self): self.plik = open(self.nazwa_pliku, self.tryb) return self.plik def __exit__(self, typ_wyjątku, wartosc_wyjątku, traceback): if self.plik: self.plik.close() with FileManager('test.txt', 'w') as f: f.write('Cześć')

Niuanse: Ważne jest, aby poprawnie obsługiwać wyjątki w __exit__, zwracając True, aby stłumić błędy, i być ostrożnym z zasobami, aby nie pozostawić ich otwartych w przypadku błędów.

Pytanie z podstępem.

Pytanie: Jeśli w bloku with wystąpi wyjątek, czy Python wywołuje metodę __exit__? Jak przekazywane są parametry błędu?

Odpowiedź: Tak, metoda __exit__ jest zawsze wywoływana, nawet jeśli występuje wyjątek. Przekazywane są do niej typ wyjątku, jego wartość i ślad stosu. Jeśli __exit__ zwraca True, wyjątek jest stłumiony.

class Simple: def __enter__(self): print('Wejście') def __exit__(self, typ_wyjątku, wartosc_wyjątku, traceback): print('Wyjście') print(typ_wyjątku, wartosc_wyjątku) return True # błąd nie "wypływa" na zewnątrz with Simple(): raise ValueError('bum!')

Przykłady rzeczywistych błędów z powodu nieznajomości niuansów tematu.


Historia

Zapomniano zaimplementować __exit__ w własnym menedżerze pracy z deskryptorami plików — w przypadku wyjątku plik nie był zamykany, co prowadziło do wycieków deskryptorów plików i awarii podczas pracy z dużą ilością plików.

Historia

Skorzystano z zewnętrznego menedżera kontekstu do zarządzania bazą danych, który zwracał True w __exit__ dla wszystkich wyjątków. To "tłumiło" błędy i prowadziło do niezauważalnych awarii i naruszeń integralności danych, ponieważ logika uważała, że transakcja zakończyła się sukcesem.

Historia

Skorzystano z dekoratora @contextmanager z modułu contextlib, ale zapomniano o obsłudze wyjątków wewnątrz yield, przez co połączenie z gniazdem pozostawało otwarte przy awarii kodu i serwer "zawieszał się" z otwartymi portami.