Як працює with x as y в Python

4 хв. читання

Це перша стаття з серії, в якій ми поступово будемо розбиратися зі внутрішнім влаштуванням фіч у 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>
Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.9K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація