Объяснение '__enter__' и '__exit__' в Python
Описание проблемы для StackOverflow:
Я увидел следующий код и не совсем понимаю, что он означает:
def __enter__(self):
return self
def __exit__(self, type, value, tb):
self.stream.close()
Кроме этого, вот полный код:
from __future__ import with_statement # для python 2.5
class a(object):
def __enter__(self):
print 'sss'
return 'sss111'
def __exit__(self, type, value, traceback):
print 'ok'
return False
with a() as s:
print s
print s
Можете объяснить, что происходит в этом коде и какую роль играют методы __enter__
и __exit__
? Почему используется конструкция with
?
4 ответ(ов)
Если вы знаете, что такое контекстные менеджеры, то вам не нужно ничего больше, чтобы понять магические методы __enter__
и __exit__
. Давайте рассмотрим очень простой пример.
В этом примере я открываю файл myfile.txt с помощью функции open. Блок try/finally гарантирует, что даже если произойдёт неожиданное исключение, файл myfile.txt будет закрыт.
fp = open(r"C:\Users\SharpEl\Desktop\myfile.txt")
try:
for line in fp:
print(line)
finally:
fp.close()
Теперь я открываю тот же файл с помощью оператора with:
with open(r"C:\Users\SharpEl\Desktop\myfile.txt") as fp:
for line in fp:
print(line)
Если вы посмотрите на код, я не закрыл файл, и здесь нет блока try/finally. Потому что оператор with автоматически закрывает myfile.txt. Вы даже можете проверить это, вызвав атрибут print(fp.closed)
— он вернёт True
.
Это происходит потому, что объекты файлов (fp в моем примере), возвращаемые функцией open, имеют два встроенных метода: __enter__
и __exit__
. Они также известны как контекстные менеджеры. Метод __enter__
вызывается в начале блока with, а метод __exit__
вызывается в конце.
Примечание: оператор with работает только с объектами, которые поддерживают протокол управления контекстом (т.е. у них есть методы __enter__
и __exit__
). Класс, который реализует оба метода, называется классом контекстного менеджера.
Теперь давайте определим собственный класс контекстного менеджера.
class Log:
def __init__(self, filename):
self.filename = filename
self.fp = None
def logging(self, text):
self.fp.write(text + '\n')
def __enter__(self):
print("__enter__")
self.fp = open(self.filename, "a+")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("__exit__")
self.fp.close()
with Log(r"C:\Users\SharpEl\Desktop\myfile.txt") as logfile:
print("Main")
logfile.logging("Test1")
logfile.logging("Test2")
Надеюсь, теперь у вас есть базовое понимание обоих магических методов __enter__
и __exit__
.
В дополнение к приведённым выше ответам для иллюстрации порядка вызовов, вот простой пример выполнения кода:
class MyClass:
def __init__(self):
print("__init__")
def __enter__(self):
print("__enter__")
def __exit__(self, ex_type, ex_value, ex_traceback):
print(f"ex_type, ex_value, ex_traceback={ex_type, ex_value, ex_traceback}")
# если возникают какие-либо ошибки, здесь можно выполнить специальную обработку
if ex_type == KeyError:
# повторно вызвать исключение для обработки в верхних вызывающих функциях
return False
# возвращая True, мы игнорируем исключение
# все остальные случаи идут к стандартному выходу, например, для очистки ресурсов и т.д.
print("__exit__")
def __del__(self):
print("__del__")
with MyClass():
print("body")
# # раскомментируйте, чтобы смоделировать специальные исключения, например KeyError для обработки в __exit__()
# raise KeyError("key error")
В результате выполнения вы получите следующий вывод:
__init__
__enter__
body
ex_type, ex_value, ex_traceback=(None, None, None)
__exit__
__del__
Обратите внимание: при использовании синтаксиса with MyClass() as my_handler
, переменная my_handler получит значение, возвращаемое методом __enter__()
, в данном случае это None
. Для таких случаев рекомендуется определить возвращаемое значение, например:
def __enter__(self):
print('__enter__')
return self
Это называется менеджер контекста, и я хотел бы добавить, что аналогичные подходы существуют и в других языках программирования. Сравнение этих подходов может помочь лучше понять менеджер контекста в Python.
В общем, менеджер контекста используется, когда мы работаем с ресурсами (файлы, сеть, база данных), которые нужно инициализировать, а затем, в какой-то момент, освободить (осуществить утилизацию). В Java 7 и выше у нас есть автоматическое управление ресурсами, которое принимает следующую форму:
// Java код
try (Session session = new Session()) {
// делаем что-то
}
Обратите внимание, что Session
должен реализовывать интерфейс AutoClosable
или один из его (многих) подинтерфейсов.
В C# у нас есть операторы using
для управления ресурсами, которые принимают следующую форму:
// C# код
using(Session session = new Session()) {
// делаем что-то
}
В этом случае Session
должен реализовывать интерфейс IDisposable
.
В Python класс, который мы используем, должен реализовывать методы __enter__
и __exit__
. Это выглядит так:
# Python код
with Session() as session:
# делаем что-то
Как уже отметили другие, вы всегда можете использовать оператор try/finally
во всех языках для реализации аналогичного механизма. Это просто синтаксический сахар.
В Python при использовании оператора with
вызов метода __enter__
происходит, когда происходит вход в контекст, и ресурсы необходимо захватить. Когда исполнение покидает этот контекст, Python вызывает метод __exit__
, чтобы освободить ресурс.
Рассмотрим менеджеры контекста и оператор with
в Python. Менеджер контекста — это простой «протокол» (или интерфейс), которому должен следовать ваш объект, чтобы его можно было использовать с оператором with
. Основная вещь, которую вам нужно сделать, — это добавить методы enter и exit в объект, если вы хотите, чтобы он функционировал как менеджер контекста. Python будет вызывать эти два метода в соответствующие моменты цикла управления ресурсами.
Посмотрим, как это может выглядеть на практике. Вот пример простой реализации менеджера контекста для функции open():
class ManagedFile:
def __init__(self, name):
self.name = name
def __enter__(self):
self.file = open(self.name, 'w')
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
Наш класс ManagedFile следует протоколу менеджера контекста и теперь поддерживает оператор with
.
>>> with ManagedFile('hello.txt') as f:
... f.write('hello, world!')
... f.write('bye now')
Python вызывает метод enter, когда исполнение входит в контекст оператора with
, и пора захватить ресурс. Когда исполнение покидает контекст, Python вызывает метод exit, чтобы освободить ресурс.
Однако написание менеджера контекста на основе класса — не единственный способ поддержать оператор with
в Python. Утилитный модуль contextlib
в стандартной библиотеке предоставляет несколько дополнительных абстракций, основанных на базовом протоколе менеджера контекста. Это может облегчить вам жизнь, если ваши случаи использования совпадают с тем, что предлагает contextlib
.
Разница между старыми и новыми классами в Python?
Что делают __init__ и self в Python?
Добавление метода к существующему экземпляру объекта в Python
Несколько переменных в операторе 'with'?
В чем разница между __init__ и __call__?