Menadżery kontekstu#
Wprowadzenie#
Często spotykany w zarządzaniu zasobami jest następujący idiom:
do_setup()
try:
do_task()
except SomeError:
handle_the_error()
finally:
do_cleanup()
Wyrażenie with
#
Aby uprościć i uodpornić się na błędy programisty, od Pythona 2.5 wzwyż dostępne jest wyrażenie with.
Menedżer kontekstu (context manager) jest odpowiedzialny za zarządzanie zasobami wewnątrz bloku kodu.
Najczęściej tworzy te zasoby na początku bloku, a zwalnia na końcu.
Na przykład, menadżer kontekstu dla plików upewnia się, że pliki zostały prawidłowo zamknięte po zakończeniu bloku, nawet jeśli zostanie zgłoszony wyjątek.
with open('myfile.txt', 'wt') as f:
f.write('foo bar')
Odpowiednikiem bloku:
with VAR = EXPR:
BLOCK
jest zapis:
VAR = EXPR
VAR.__enter__()
try:
BLOCK
finally:
VAR.__exit__()
Protokół menadżera kontekstu#
Menedżer kontekstu jest klasą posiadającą dwie metody specjalne:
__enter__
- metoda wywoływana na samym początku bloku wewnątrz with.__exit__
- metoda jest odpowiednikiemfinally:
, wywoływana po zakończeniu blokuwith
.
Poniżej przedstawiono przykładowy, prosty menadżer kontekstu:
class Context:
def __init__(self):
print('__init__()')
def __enter__(self):
print('__enter__()')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('__exit__()')
with Context():
print("Doing work inside context")
__init__()
__enter__()
Doing work inside context
__exit__()
Metoda __enter__
#
Wartością zwracaną przez menadżera kontekstu w funkcji __enter__
może być obiekt, który zostanie przypisany do zmiennej występującej po as:
import sys
def blackhole(*args, **kwargs):
pass
class SuppressOutput:
def __enter__(self):
print('SuppressOutput.__enter__()')
self.write, sys.stdout.write = sys.stdout.write, blackhole
return self.write
def __exit__(self, exc_type, exc_val, exc_tb):
sys.stdout.write = self.write
print('SuppressOutput.__exit__()')
with SuppressOutput() as stdout_write:
print('That won\'t be printed')
stdout_write('But this one will be printed\n')
SuppressOutput.__enter__()
But this one will be printed
SuppressOutput.__exit__()
Metoda __exit__
#
Do metody __exit__
trafia informacja o wyjątkach, jakie pojawiły się bloku
with
.
Jeśli metoda
__exit__
zwracatrue
, to wyjątek został obsłużony przez menadżera kontekstu.Jeśli zwrócona zostanie wartość
false
, to wyjątek będzie propagowany dalej.
class Context:
def __enter__(self):
pass
def __exit__(self, excpt_type, excpt_val, excpt_tb):
print("Exception type:", excpt_type)
print("Exception value:", excpt_val)
print("Traceback object:", excpt_tb)
return True # or False
with Context():
x = 2
Exception type: None
Exception value: None
Traceback object: None
with Context():
x = 2 / 0
Exception type: <class 'ZeroDivisionError'>
Exception value: division by zero
Traceback object: <traceback object at 0x7f8a880f18c0>
contextlib.contextmanager#
W prostych przypadkach zamiast tworzyć klasę, możemy skorzystać z gotowego dekoratora zawartego w module contextlib, który konwertuje składnię funkcji do postaci menadżera kontekstu:
from contextlib import contextmanager
@contextmanager
def make_context():
try:
prepare_resource()
yield context_object
except RuntimeError as err:
handle_exception_here()
finally:
do_clean_up()
Przykładowy prosty menadżer kontekstu napisany z użyciem contextmanager
:
from contextlib import contextmanager
@contextmanager
def Shouter():
print('Going in')
yield
print('Coming out')
with Shouter():
print('Inside')
Going in
Inside
Coming out
Jeżeli chcemy obsłużyć rzucone przez funkcję wyjątki, możemy to zrobić w następujący sposób:
@contextmanager
def Shouter():
print('Going in')
try:
yield
except Exception:
print('Error!')
else:
print('No error')
with Shouter():
pass
Going in
No error
with Shouter():
print(1/0)
Going in
Error!