PEP 343 - The “with” statement
https://docs.python.org/3/library/stdtypes.html#context-manager-types
https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
W Pythonie 2.6 i Pythonie 3 pojawiła się nowa instrukcja związana z wyjątkami - with z opcjonalnym as [PEP 343]. Instrukcja została zaprojektowana do pracy z obiektami menedżerów kontekstu obsługującymi protokół oparty na metodach. Instrukcja with/as ma być alternatywą dla zwykłego zastosowania try/finally.
# Składnia podstawowa. # Wyrażenie ma zwracać obiekt obsługujący protokół zarządzania kontekstem. with wyrażenie [as zmienna]: instrukcje
# Czytanie danych z pliku. # Plik zostanie zamknięty nawet gdy wystąpi wyjątek. with open(infile_name, 'r') as infile: read_data = infile.read()
# How to open a file using the open with statement. # http://stackoverflow.com/questions/9282967/how-to-open-a-file-using-the-open-with-statement def filter(text, infile_name, outfile_name): '''Read a list of names from a file line by line into an output file. If a line begins with a particular name, insert a string of text after the name before appending the line to the output file. ''' with open(outfile_name, 'w') as outfile: with open(infile_name, 'r', encoding='utf-8') as infile: for line in infile: if line.startswith(text): line = line[0:len(text)] + ' - Truly a great person!\n' outfile.write(line) # W Py2.7 i Py3.1+ można użyć wielokrotnie open(), # co jest równoważne zagnieżdżaniu with. # http://docs.python.org/reference/compound_stmts.html#the-with-statement with open(outfile_name, 'w') as outfile, \ open(infile_name, 'r', encoding='utf-8') as infile: instrukcje
Opis działania instrukcji with.
# Przykład działania protokołu zarządzania kontekstem. class ContextManager: def __init__(self): # metoda opcjonalna pass def message(self, argument): print("wykonywanie {}".format(argument)) def __enter__(self): print("rozpoczęcie bloku with") return self # czasem może być inny obiekt def __exit__(self, exception_type, exception_value, exception_traceback): # typ, wartość, ślad wyjątku if exception_type is None: # brak wyjątku print("normalne wyjście") return True else: print("zgłoszenie wyjątku {}".format(exception_type)) return False # przekazanie wyjątku # return True # porzucenie wyjątku # Zastosowanie. with ContextManager() as context: context.message("test 1") print("wiersz osiągnięty") with ContextManager() as context: context.message("test 2") raise TypeError print("wiersz nie zostanie osiągnięty") print("po with ...")
Menedżery kontekstu są zaawansowanymi narzędziami, przeznaczonymi dla osób tworzących narzędzia. Dodatkowe narzędzia służące do tworzenia kodu menedżerów kontekstu udostępnia moduł contextlib [PEP 343].
https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRaises
W Pythonie 2.7 i 3.1 pojawiła się możliwość użycia assertRaises() jako menedżera kontekstu. Można badać szczegóły wyzwolonego w teście wyjątku. Warto prześledzić przykłady z dokumentacji [25.3 unittest].
with self.assertRaises(SomeException) as context: instrukcja # pojedyńcza instrukcja wyzwalająca wyjątek # Sprawdzenie pewnego kodu błędu (na zewnątrz instrukcji with). self.assertEqual(context.exception.error_code, 3) # Można sprawdzić, czy dostaliśmy daną klasę wyjątku, # czy może jego rodzica. self.assertEqual(context.exception.__class__, SomeException) self.assertTrue(isinstance(context.exception, SomeException)) # Sprawdzenie występowania pewnego tekstu w komunikacie. self.assertTrue("NoneType" in str(context.exception))
W Pythonie 3.4 pojawił się menedżer kontekstu subTest(), który pomaga tworzyć testy o bardziej czytelnych komunikatach. Zwykle używa się subTest(), gdy mamy wiele mało różniących się testów, np. zmieniają się tylko parametry wywołania danej funkcji.
# https://docs.python.org/3/library/unittest.html # https://www.caktusgroup.com/blog/2017/05/29/subtests-are-best/ def is_even(n): return n % 2 == 0 class NumbersTest(unittest.TestCase): def test_even(self): """ Test that numbers between 0 and 5 are all even. """ for i in range(0, 6): with self.subTest(i=i): # Py3 self.assertEqual(i % 2, 0) # self.assertTrue(is_even(i)) # wersja z funkcją # Dzięki subTest() otrzymamy informację dla jakiego 'i' były błędy.
W pewnych sytuacjach potrzebne są działania czyszczące na końcu przetwarzania, które zostaną wykonane niezależnie od wystąpienia wyjątków. W ramach jednego modułu można wykorzystać try/except/finally lub menedżera kontekstu with.
Jeżeli pewne działania mają być wykonane przy normalnym zakończeniu działania interpretera, dawny sposób polegał na rejestrowaniu jednej funkcji bezparametrowej func() w funkcji systemowej sys.exitfunc(func). Obecnie zalecanym sposobem działania jest korzystanie z modułu 'atexit', dzięki któremu można rejestrować wiele funkcji wykonujących prace czyszczące. Moduł 'atexit' jest obecny w Pythonie 2 i 3.
# https://docs.python.org/3/library/atexit.html # https://pymotw.com/2/atexit/ import atexit def my_cleanup(name): print('my_cleanup({})'.format(name)) atexit.register(my_cleanup, 'first') atexit.register(my_cleanup, 'second') atexit.register(my_cleanup, 'third') # Komunikaty po normalnym zakończeniu pracy interpretera Pythona: # my_cleanup(third) # działają od końca, jak stos # my_cleanup(second) # my_cleanup(first)