Объяснение 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

результат на стороне сервера такой же как и раньше

извините за мой плохой английский и мои неясные объяснения, спасибо....