Объяснение Python '__enter__' и '__exit__'
Я видел это в чей-то код. Что это значит?
def __enter__(self):
return self
def __exit__(self, type, value, tb):
self.stream.close()
from __future__ import with_statement#for python2.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
Ответы
Ответ 1
Использование этих магических методов (__enter__
, __exit__
) позволяет реализовать объекты, которые можно легко использовать с помощью оператора with
.
Идея состоит в том, что она упрощает сбор кода, который требует какого-то "очищенного" кода (подумайте об этом как блок try-finally
). Еще несколько объяснений здесь.
Полезным примером может быть объект подключения к базе данных (который затем автоматически закрывает соединение после того, как соответствующий оператор "с" выходит за пределы области видимости):
class DatabaseConnection(object):
def __enter__(self):
# make a database connection and return it
...
return self.dbconn
def __exit__(self, exc_type, exc_val, exc_tb):
# make sure the dbconnection gets closed
self.dbconn.close()
...
Как объяснялось выше, используйте этот объект с оператором with
(вам может понадобиться сделать from __future__ import with_statement
в верхней части файла, если вы находитесь на Python 2.5).
with DatabaseConnection() as mydbconn:
# do stuff
PEP343 - "with" statement" также имеет хорошую запись.
Ответ 2
Если вы знаете, что контекстные менеджеры, вам больше не нужно разбираться в магических методах __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 open(r"C:\Users\SharpEl\Desktop\myfile.txt") as fp:
for line in fp:
print(line)
Если вы посмотрите на код, я не закрыл файл, и нет блока try/finally. Поскольку оператор с автоматически закрывает myfile.txt. Вы даже можете проверить его, вызвав атрибут print(fp.closed)
, который возвращает True
.
Это связано с тем, что файловые объекты (fp в моем примере), возвращаемые функцией open, имеют два встроенных метода __enter__
и __exit__
. Он также известен как менеджер контекста. __enter__
метод вызывается в начале с, а метод __exit__
вызывается в конце. Примечание. Оператор с работает только с объектами, которые поддерживают протокол сопоставления контекста, т.е. Имеют методы __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__
.
Ответ 3
Мне было странно трудно найти документы по python для __enter__
и __exit__
помощью Googling, поэтому для помощи другим здесь есть ссылка:
https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers
https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
(детали одинаковы для обеих версий)
object.__enter__(self)
Введите контекст выполнения, связанный с этим объектом. Оператор with
свяжет возвращаемое значение этого метода с целью (ами), указанными в предложении as оператора, если таковые имеются.
object.__exit__(self, exc_type, exc_value, traceback)
Выход из контекста времени выполнения, связанного с этим объектом. Параметры описывают исключение, которое вызвало выход из контекста. Если контекст был закрыт без исключения, все три аргумента будут None
.
Если предоставлено исключение, и метод желает подавить исключение (т.е. предотвратить его распространение), он должен вернуть истинное значение. В противном случае исключение будет обработано нормально при выходе из этого метода.
Обратите внимание, что __exit__()
не должны вызывать переданное исключение; это ответственность звонящих.
Я надеялся на четкое описание аргументов метода __exit__
. Этого не хватает, но мы можем вывести их...
Предположительно exc_type
- это класс исключения.
В нем говорится, что вы не должны повторно поднимать переданное исключение. Это говорит нам о том, что один из аргументов может быть фактическим экземпляром Exception... или, может быть, вы должны создать его экземпляр самостоятельно по типу и значению?
Мы можем ответить, посмотрев на эту статью:
http://effbot.org/zone/python-with-statement.htm
Например, следующий __exit__
поглощает любую ошибку TypeError, но пропускает все другие исключения:
def __exit__(self, type, value, traceback):
return isinstance(value, TypeError)
... так ясно, что value
является экземпляром исключения.
И, вероятно, traceback
- это объект трассировки Python.
Ответ 4
В дополнение к приведенным выше ответам, чтобы проиллюстрировать порядок вызова, простой пример запуска
class myclass:
def __init__(self):
print("__init__")
def __enter__(self):
print("__enter__")
def __exit__(self, type, value, traceback):
print("__exit__")
def __del__(self):
print("__del__")
with myclass():
print("body")
Производит вывод:
__init__
__enter__
body
__exit__
__del__
Напоминание: при использовании синтаксиса with myclass() as mc
переменная mc получает значение, возвращаемое __enter__()
, в вышеприведенном случае None
! Для такого использования необходимо определить возвращаемое значение, например:
def __enter__(self):
print('__enter__')
return self
Ответ 5
попробуйте добавить мои ответы (моя мысль об обучении):
__enter__
и [__exit__]
оба являются методами, которые вызываются при входе и выходе из тела " оператора with " (PEP 343), а реализация обоих называется диспетчером контекста.
Оператор with предназначен для сокрытия управления потоком предложения try finally и делает код непостижимым.
синтаксис оператора with:
with EXPR as VAR:
BLOCK
что переводится как (как упомянуто в PEP 343):
mgr = (EXPR)
exit = type(mgr).__exit__ # Not calling it yet
value = type(mgr).__enter__(mgr)
exc = True
try:
try:
VAR = value # Only if "as VAR" is present
BLOCK
except:
# The exceptional case is handled here
exc = False
if not exit(mgr, *sys.exc_info()):
raise
# The exception is swallowed if exit() returns true
finally:
# The normal and non-local-goto cases are handled here
if exc:
exit(mgr, None, None, None)
попробуйте немного кода:
>>> import logging
>>> import socket
>>> import sys
#server socket on another terminal / python interpreter
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> s.listen(5)
>>> s.bind((socket.gethostname(), 999))
>>> while True:
>>> (clientsocket, addr) = s.accept()
>>> print('get connection from %r' % addr[0])
>>> msg = clientsocket.recv(1024)
>>> print('received %r' % msg)
>>> clientsocket.send(b'connected')
>>> continue
#the client side
>>> class MyConnectionManager:
>>> def __init__(self, sock, addrs):
>>> logging.basicConfig(level=logging.DEBUG, format='%(asctime)s \
>>> : %(levelname)s --> %(message)s')
>>> logging.info('Initiating My connection')
>>> self.sock = sock
>>> self.addrs = addrs
>>> def __enter__(self):
>>> try:
>>> self.sock.connect(addrs)
>>> logging.info('connection success')
>>> return self.sock
>>> except:
>>> logging.warning('Connection refused')
>>> raise
>>> def __exit__(self, type, value, tb):
>>> logging.info('CM suppress exception')
>>> return False
>>> addrs = (socket.gethostname())
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> with MyConnectionManager(s, addrs) as CM:
>>> try:
>>> CM.send(b'establishing connection')
>>> msg = CM.recv(1024)
>>> print(msg)
>>> except:
>>> raise
#will result (client side) :
2018-12-18 14:44:05,863 : INFO --> Initiating My connection
2018-12-18 14:44:05,863 : INFO --> connection success
b'connected'
2018-12-18 14:44:05,864 : INFO --> CM suppress exception
#result of server side
get connection from '127.0.0.1'
received b'establishing connection'
и теперь попробуйте вручную (следуя синтаксису перевода):
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #make new socket object
>>> mgr = MyConnection(s, addrs)
2018-12-18 14:53:19,331 : INFO --> Initiating My connection
>>> ext = mgr.__exit__
>>> value = mgr.__enter__()
2018-12-18 14:55:55,491 : INFO --> connection success
>>> exc = True
>>> try:
>>> try:
>>> VAR = value
>>> VAR.send(b'establishing connection')
>>> msg = VAR.recv(1024)
>>> print(msg)
>>> except:
>>> exc = False
>>> if not ext(*sys.exc_info()):
>>> raise
>>> finally:
>>> if exc:
>>> ext(None, None, None)
#the result:
b'connected'
2018-12-18 15:01:54,208 : INFO --> CM suppress exception
результат на стороне сервера такой же как и раньше
извините за мой плохой английский и мои неясные объяснения, спасибо....