Блог

Заметка/Python: оператор with

В python 2.5 был добавлен замечательный оператор with. Оператор with очень часто встречается в коде.

Хочу рассмотреть как его можно использовать.

К примеру, чтение файла:

with open("x.txt") as f:
     data = f.read()
     do_something_with(data)

Очень компактная запись и это замена

>>> f = open("x.txt")
>>> f
<open file 'x.txt', mode 'r' at 0x00AE82F0>

>>> f.__enter__()
<open file 'x.txt', mode 'r' at 0x00AE82F0>

>>> 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 работает как Exseption. То есть:

do_init()

try:
     do_task()
except SomeError :
     do_exception_handling()
finally:
     do_exit()

Блок with работает с объектами, значит все что пожелаешь запихнуть не получится. Для того чтобы корректно отработал блок нужно добавить в ваш объект, дополнительно еще два метода __enter__, __exit__.

Вот как должен выглядит для наглядности.

class controlled_execution:
    def __init__(self):
        pass
    def __enter__(self):
        set_things_up_return_thing
    def __exit__(self, type, value, traceback):
        tear_things_down

with controlled_execution() as thing:
    some_code

Т.е. мы сначала инициализируем наш ресурс метод __init__, потом выполняем попытку работы с нашим ресурсом в блоке try..except..finally внутри блока оператора with. Если возник ексепшен то попадаем в ветку except и обрабатываем ошибку метод __exit__. По-окончанию выполняется ветка finally метод __exit__ где производим зачистку и освобождение нашего ресурса.

Пример который покажет как все наглядно работает:

class Logger(object):
    def __init__(self, value):
        self.value = value
        print "%d t metod __init__" % self.value

    def __enter__(self):
        print "%d t metod __enter__" % self.value

    def __exit__(self, type, exception, trace):
        print "%d t metod __exit__" % self.value
        if type == ValueError:
            print "%d - Error, call method __exit__" % self.value
            return True

for i in range(3):
    with  Logger(i) as log:
        if i == 1: raise ValueError()

Result:
0 metod __init__
0 metod __enter__
0 metod __exit__
1 metod __init__
1 metod __enter__
1 metod __exit__
1 – Error, call method __exit__
2 metod __init__
2 metod __enter__
2 metod __exit__