Tworząc oprogramowanie często spotykamy się z sytuacją gdy chcemy uzyskać dostęp oraz zarządzać pewnymi zasobami. Takimi jak na przykład operacje na plikach czy połączenie do bazy danych. Zasoby te często posiadają pewne ograniczenia więc dobrą praktyką jest ich zwolnienie zaraz po zakończeniu ich używania. Python pozwala nam na łatwe zarządzanie tymi zasobami dostarczając nam pomoc, którą jest menadżer kontekstu.
Dobrym przykładem opisanych we wstępie ograniczeń jest próba otwarcia wielu plików na raz. Spróbuj wykonać następujący program:
fh = []
for i in range(10000):
fh.append(open('log.txt', 'w'))
W tym momencie prawdopodobnie zobaczysz następujący wyjątek:
Traceback (most recent call last):
File "", line 2, in
OSError: [Errno 24] Too many open files: 'log.txt'
Jest to spowodowane tym, że system nakłada na procesy ograniczenia tego ile plików może na raz otworzyć jeden proces. W tym momencie jednak pozostaliśmy z listą wielu już otwartych plików, które i tak powinny zostać zamknięte. Moglibyśmy dodać do naszego kodu strukturę try-except-finally, jednak może ona wpłynąć negatywnie na czytelność naszego kodu.
Dlatego często otwierając pliki korzystamy właśnie z menadżerów kontekst. Menadżer kontekstu udostępniany przez funkcję open()
jest jednym z najpopularniejszych, dlatego prawdopodobnie mieliście już okazję się z nim spotkać nawet jeżeli jesteście początkującymi użytkownikami Pythona.
Zastosowanie tego narzędzia może wyglądać w następujący sposób:
with open("log.txt") as f:
data = f.read()
Poza wykorzystaniem już istniejących menadżerów kontekstu przy pomocy klauzuli with, możemy też oczywiście napisać nasz własny menadżer. Istnieją dwa sposoby w jaki możemy to zrobić, napisać klasę lub funkcję.
Menadżer kontekstu jako klasa
Pierwszym ze sposobów na napisanie własnego menadżera kontekstu jest napisanie odpowiedniej klasy. Musi ona zawierać metody__enter__
i __exit__
. Opowiadają one za kolejno to co wydarzy się przed oraz po wykonaniu kodu znajdującego się w bloku with. Metoda __enter__
powinna zwrócić zasoby, na których będziemy operować wewnątrz bloki with, a metoda __exit__
nie musi nic zwracać, a jej zadaniem jest posprzątanie na koniec.
Na początek spróbujmy napisać prosty menadżer, który może być przydatny podczas wykonywania operacji na plikach. Będzie on dział na podobnej zasadzie jak ten z poprzedniego przykładu jednak rozszerzy jego funkcjonalność o dodatkowe logowanie stanu.
class FileManager():
def __init__(self, filename, mode):
print("Metoda __init__")
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
print("Metoda __enter__")
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, type, value, traceback):
print("Metoda __exit__")
self.file.close()
with FileManager("log.txt", 'r') as f:
print("Blok with")
data = f.read()
W metodzie __init__
tworzymy zmienne, które przechowują informacje dotyczące zasobów którymi chcemy zarządzać. Metoda __enter__
odpowiada za otwarcie i udostępnienie zasobów (w tym przypadku uchwytu do otwartego pliku), a metoda __exit__
tak jak już była o tym mowa wcześniej – sprząta, czyli w naszym przykładzie zamyka plik. Dzięki dodatkowym funkcjom print możesz łatwo zobaczyć w jaki sposób wywoływane są kolejne funkcje.
W następnym kroku spróbuj wywołać wyjątek wewnątrz bloku with z powyższego przykładu (możesz to zrobić na przykład dodając linijkę raise Exception
). Zobaczysz, że metoda __exit__
zostanie wywołana, a dopiero w dalszej kolejności pojawi się informacja o wyjątku.
Jeżeli chcesz, żeby menadżer kontekstu sam obsługiwał wyjątki możesz zrobić to wewnątrz metody __exit__
korzystając z trzech argumentów które do niej przekazujemy. W naszym przykładzie to: type
, value
i traceback
. Gdy skorzystasz z tej możliwości, aby zapobiec przesłaniu wyjątku poza blok with upewnij się, że funkcja __exit__
zwróci wartość True
.
Menadżer kontekstu jako funkcja
Drugim sposobem na stworzenie menadżera kontekstu jest napisanie generatora, czyli funkcji zawierającej instrukcję yield. W tym celu będziemy musieli wykorzystać dekorator contextmanager który znajduje się w bibliotece contextlib. Metoda ta nieco upraszcza zapis i z pewnością dobrze nadaje się do tworzenia prostych menadżerów.
Na początek spójrzmy na przykładowy kod:
from contextlib import contextmanager
@contextmanager
def file_manager(name, mode='w'):
f = open(name, mode)
yield f
f.close()
Składa się on z kilku części, które są warte są krótkiego wytłumaczenia:
- Instrukcja
yield
sprawia, że Python traktuje tą funkcję jako generator, dzięki czemu możliwy jest powrót do jej dalszej części po zwróceniu wartości. - Dekorator
contextmanager
nadpisuję naszą funkcję w taki sposób, że odwołując się do niej otrzymamy obiekt typu
GeneratorContextManager. - Dzięki temu możemy wykorzystać naszą funkcję jako menadżer kontekstu używając instrukcji
with
.
Teraz prawdopodobnie wiesz już wszystko co potrzebne jest do używania menadżerów kontekstu. Jeżeli masz jakiekolwiek pytania zapraszam do komentowania i życzę Ci powodzenia w dalszej nauce Pythona 🙂
Źródła:
https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
https://docs.python.org/3/library/contextlib.html
Jeżeli chcesz dowiedzieć się więcej o programowaniu w Pythonie zapraszam Cię do przeczytania artykułu na temat tego jak używać args i kwargs w naszych funkcjach.
Brak komentarzy
You can leave the first : )