Це перша стаття з серії, в якій ми поступово будемо розбиратися зі внутрішнім влаштуванням фіч у Python. Сьогодні у нас конструкція with x as y
, а в наступних матеріалах: метакласи, ексепшени, логування та багато іншого.
Починаючи з доволі старої версії 2.5, в Python є конструкція with x as y:
. Сьогодні ми розглянемо як вона працює і як інтегрувати її в свої проекти.
Як і все в Python, ця конструкція дуже проста. Достатньо лише зрозуміти, яку проблему вона вирішує. Поглянемо на цей код:
set things up
try:
do something
finally:
tear things down
Тут "set things up" - означає відкриття файлу або іншого стороннього ресурсу, а "tear things down" - його закриття. Конструкція try-finally
гарантує, що "tear things down" виконається забудь-яких умов, навіть якщо основний код впаде з помилкою.
Якщо ви робите це часто, то, напевно, захочете винести "set things up" та "tear things down" в окрему функцію, що зробить код більше придатним до повторного використання. Ви, звісно, можете зробити щось таке:
def controlled_execution(callback):
set things up
try:
callback(thing)
finally:
tear things down
def my_function(thing):
do something
controlled_execution(my_function)
Але це незручно, особливо коли вам потрібен доступ до локальних змінних. Інший підхід, це використовувати генератор та конструкцію for-in
для "обгортки коду":
def controlled_execution():
set things up
try:
yield thing
finally:
tear things down
for thing in controlled_execution():
do something with thing
Але yield
не працює в середині try-finally
в версії 2.4 та старших. І хоча це можна виправити (що й зробили в 2.5), але це дивно: використовувати генератор, знаючи, що він викликається лише раз.
Тож, після перебору альтернатив, GvR та the python-dev team нарешті вигадали узагальнення останнього, використовуючи об'єкт замість генератора для управління зовнішньою частиною коду:
class controlled_execution:
def __enter__(self):
set things up
return thing
def __exit__(self, type, value, traceback):
tear things down
with controlled_execution() as thing:
some code
Тепер, коли конструкція "with" виконується, Python вираховує вираження, викликає метод __enter__
на отриманому значенні (яке ще називають "context guard"), і присвоює все, що повернув __enter__
до змінної, переданої через as
. Потім виконається код всередині конструкції, а за ним в любому випадку викликається метод __exit__
.
І в якості бонусу, метод __exit__
може отримати доступ до exception - пропустити його або подавити. Щоб подавити, потрібно просто повернути True
. Наприклад, наступний код ігнорує всі TypeError, але пропускає решту:
def __exit__(self, type, value, traceback):
return isinstance(value, TypeError)
В Python 2.5 до файлового об'єкту додали методи __enter__
та __exit__
, що дозволяє використовувати його з конструкцією with
:
>>> f = open("x.txt")
>>> f
<open 'r'="" 'x.txt',="" 0x00ae82f0="" at="" file="" mode="">
>>> f.__enter__()
<open 'r'="" 'x.txt',="" 0x00ae82f0="" at="" file="" mode="">
>>> f.read(1)
'X'
>>> f.__exit__(None, None, None)
>>> f.read(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file
Тобто для того, щоб відкрити файл і робити над ним якісь операції достатньо цього:
with open("x.txt") as f:
data = f.read()
do something with data
```</module></stdin></open></open>
Ще немає коментарів