У цій частині циклу статей про синтаксичний цукор Python ми детально розглянемо роботу оператора with
.
Переглядаючи байт-код для простого оператора with
, ви помітите, що застосовується багато інструкцій opcode. Більшість із них — це керування стеком виконання для інтерпретатора та обробка можливих винятків. Тому ми не зачіпатимемо байт-код як такий, а натомість розглянемо пояснення синтаксису, як це показано у посиланні:
# with a as b:
# c
_enter = type(a).__enter__
_exit = type(a).__exit__
b = _enter(a)
try:
c
except:
if not _exit(a, *sys.exc_info()):
raise
else:
_exit(a, None, None, None)
Розв'язання з оператором with
Щоб проілюструвати, як все це працює, ми скористаємося класичним прикладом замикання (lock) RAII у цьому дописі. RAII означає «Отримання ресурсу є ініціалізація». Тобто під час створення об'єкта щось відбувається, а під час вивільнення/видалення об'єкта щось скасовується.
У замиканнях це означає отримання замикання (алокацію) під час створення, а потім його звільнення (деалокацію) під час виділення. Python як мова не має гарантованої семантики очищення з погляду часу та порядку. Тому були введені контекстні менеджери, щоб явно додати до Python мовну конструкцію, яка дає нам переваги концепції RAII.
import threading
lock = threading.Lock()
with lock: # Замикання утримується.
pass # Щось, що потребує замикання.
# Замикання починає діяти.
Існує дві частини для контекстного менеджера. Одна з них викликає його спеціальним методом __enter__
під час введення блоку with; у нашому прикладі замикання це викликає threading.Lock.acquire()
. Якщо ви застосовуєте умову as
контекстного менеджера, об'єкт, який повертає метод __enter__
прив'язується до вказаної нами змінної.
Друга частина контекстного менеджера — це спеціальний метод __exit__
, він застосовується під час виклику блоку with. У нашому прикладі замикання це викликає threading.Lock.release()
.
Головна особливість __exit__
— це визначення, чи створено в тілі блоку with
виняток. Якщо виняток є, він передається складовими частинами, як повертається *sys.exc_info()
до __exit__
; якщо виняток не створюється, тоді до цих трьох частин передається None
. Якщо виняток виникає у тілі менеджера контексту, метод __exit__
може стримати чи дозволити поширення винятку.
Яку саме дію застосувати, визначається тим, чи повертає метод значення true чи false відповідно. В цьому полягає завдання поверненого значення, тож винятки будуть відновлені завдяки тому, що методи типово повертають None
.
Якщо ви хотіли б дізнатися більше історичних відомостей, PEP 343 — це те, що додали контекстні менеджери до Python.
Ще немає коментарів