Menadżer kontekstu – Słów kilka o instrukcji with

Menadżer kontekstu - przykład

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:

  1. 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.
  2. Dekorator contextmanager nadpisuję naszą funkcję w taki sposób, że odwołując się do niej otrzymamy obiekt typu
    GeneratorContextManager.
  3. 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 : )



Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *