Разбираемся с "with" в питоне (перевод) | Блог python программиста
Изображение гика

Блог питониста

Разбираемся с "with" в питоне (перевод)

18 февраля 2018 г.

Данная статья - перевод c моим небольшим дополнением, оригинал

Часто ключевое слово with не до конца понятно даже опытным разработчикам.

Как и многие другие вещи в Python, ключевое слово with на самом деле очень просто устроено, это станет очевидно, как только вы поймете какую проблему оно решает. Посмотрите на данный код:

1set things up
2try:
3    do something
4finally:
5    tear things down

Здесь под "set things up" подразумевается открытие файла, подключение какого-то внешнего ресурса, а под "tear things down" - закрытие файла, отключение от внешнего ресурса. Конструкция try-finally гарантирует, что "tear things down" часть будет всегда исполнена, даже если код, делающий что-либо вызовет ошибку или не завершится.

Если это часто ипользуется, то было бы удобно вынести код  “set things up” и “tear things down” в библиотечную функцию, чтобы легко ее использовать. Конечно, вы можете сделать что-то вроде:

 1def controlled_execution(callback):
 2    set things up
 3    try:
 4        callback(thing)
 5    finally:
 6        tear things down
 7
 8def my_function(thing):
 9    do something
10
11controlled_execution(my_function)

Но это немного многословно, особенно если вам нужно изменять локальные переменные. Другой подход заключается в использовании генератора, а затем нужно использовать for-in:

1def controlled_execution():
2    set things up
3    try:
4        yield thing
5    finally:
6        tear things down
7
8for thing in controlled_execution():
9    do something with thing

Но yield нельзя было использовать внутри try-finally в 2.4 и раньше. И немного странно использовать loop для чего-то, что вы хотите выполнить один раз.

Поэтому, после рассмотрения нескольких вариантов, Гвидо Ван Россум и python-dev команда наконец решили использовать объект вместо генератора, чтобы контролировать поведение данного кода:

1class controlled_execution:
2    def __enter__(self):
3        set things up
4        return thing
5    def __exit__(self, type, value, traceback):
6        tear things down
7
8with controlled_execution() as thing:
9    some code

Теперь, когда "with" выражение исполняется, Python исполняет выражение, вызывает метод __enter__ с полученным значением (которое называется "context guard"), затем присваивает переменной переданной словом as (в данном случае thing) то, что возвращает метод __enter__. Далее, Python исполняет тело (в данное случае some code), и в любом случае вызывает метод __exit__.

В добавок, __exit__ может подавить исключение, вернуть вместо него True. Например, этот __exit__ заменяет TypeError, но разрешает все другие исключения:

1def __exit__(self, type, value, traceback):
2    return isinstance(value, TypeError)

Например:

 1class controlled_execution:
 2    def __enter__(self):
 3        print('in enter')
 4        print(self)
 5        return ('test')
 6
 7    def __exit__(self, type, value, traceback):
 8        print('in exit')
 9
10
11with controlled_execution() as thing:
12    print('in here')
13    print(thing)
14    raise TypeError
alex@vostro:~/projects/blog_materials$ python test.py
in enter
<__main__.controlled_execution instance at 0x7f338bcb6a70>
in here
test
in exit
Traceback (most recent call last):
  File "test.py", line 14, in <module>
    raise TypeError
TypeError

Но:

 1class controlled_execution:
 2    def __enter__(self):
 3        print('in enter')
 4        print(self)
 5        return ('test')
 6
 7    def __exit__(self, type, value, traceback):
 8        print('in exit')
 9        return isinstance(value, TypeError)
10
11
12with controlled_execution() as thing:
13    print('in here')
14    print(thing)
15    raise TypeError
alex@vostro:~/projects/blog_materials$ python test.py
in enter
<__main__.controlled_execution instance at 0x7ff3fac74a70>
in here
test
in exit

В Python 2.5 у объекта типа file появились методы __enter__ и __exit__, первый просто возвращает сам объект, а второй закрывает файл:

>>> 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

Поэтому, чтобы открыть файл, сделать что-то с содержимым и точно закрыть его, вы просто делаете так:

1with open("x.txt") as f:
2    data = f.read()
3    do something with data

Это не было очень сложно, правда?

Метки

перевод python
Если вам понравился пост, можете поделиться им в соцсетях: