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

person zjm1126    schedule 31.12.2009    source источник


Ответы (7)


Использование этих волшебных методов (__enter__, __exit__) позволяет вам реализовать объекты, которые можно легко использовать с оператором with.

Идея состоит в том, что это упрощает создание кода, который требует выполнения некоторого «очищающего» кода (воспринимайте его как блок try-finally). Дополнительные объяснения здесь.

Полезным примером может быть объект подключения к базе данных (который затем автоматически закрывает соединение, как только соответствующий оператор with выходит за пределы области видимости):

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" также имеет хорошее описание.

person ChristopheD    schedule 31.12.2009
comment
Вероятно, __enter__ должен всегда возвращать self, поскольку тогда в контексте могут быть вызваны только другие методы класса. - person ViFI; 09.07.2016
comment
@ViFI В PEP 343 есть 4 примера def __enter__(self), и никто не делает return self: python. org / dev / peps / pep-0343. Почему ты так думаешь? - person Grief; 10.07.2016
comment
@Grief: по 2 причинам, на мой взгляд, 1) я не смогу вызывать другие методы для объекта self, как описано здесь: stackoverflow.com/questions/38281853/ 2) self.XYZ - это просто часть объекта self, и возвращающий дескриптор только для того, что кажется мне неуместным с точки зрения обслуживания. Я бы предпочел вернуть дескриптор завершенного объекта, а затем предоставить общедоступные API только для тех компонентов self объекта, которые я хочу предоставить пользователю, как в with open(abc.txt, 'r') as fin: content = fin.read() - person ViFI; 11.07.2016
comment
Файловые объекты возвращают self из __enter__, поэтому вы можете обрабатывать файл как f внутри with open(...) as f - person holdenweb; 31.10.2016
comment
Я должен был понять одну тонкость: если объекту требуются параметры для инициализации, они должны быть в init, а не в self. - person dfrankow; 19.02.2017
comment
Существует множество вариантов использования, чтобы не возвращать self, и в любом случае, когда вам действительно нужен сам объект контекста, вы всегда можете создать экземпляр контекста в строке перед with inst. - person meawoppl; 02.03.2018
comment
ИМО, то, что возвращается с __enter__, должно быть на усмотрение разработчика (-ов) класса, если документированное использование кажется логичным для пользователя (-ей) класса: 1. Откройте ресурс, который не предназначен для того, чтобы оставаться открытым 2. Использовать ресурс, вызывать определенные методы на ресурсе и т. Д. 3. Ресурс закрыт - person a135; 10.03.2021

Если вы знаете, что такое менеджеры контекста, вам больше ничего не нужно, чтобы понимать __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__ магических методов.

person N Randhawa    schedule 31.07.2016

Мне было странно трудно найти документы 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 - это экземпляр Exception.

И предположительно traceback является объектом трассировки Python.

person Anentropic    schedule 06.06.2017
comment
Согласен. Этот URL так сложно найти. - person Shihao Xu; 03.05.2018
comment
может быть важно отметить этот скрытый бит в ссылке PEP с указанием использования arg: python.org/dev/peps/pep-0343/#generator-decorator - person Tcll; 22.07.2019

В дополнение к приведенным выше ответам, чтобы проиллюстрировать порядок вызова, простой пример запуска

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
person Yuri Feldman    schedule 08.01.2018
comment
И даже если последовательность определений изменится, порядок выполнения останется прежним! - person Sean; 05.02.2018
comment
Это было очень полезно. Спасибо. - person Reez0; 04.04.2020
comment
Это был лучший для меня - большое спасибо - прекрасный пример: ¬) - person joe_evans; 02.07.2020

попробуйте добавить мои ответы (мои мысли об обучении):

__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

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

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

person Wira Bhakti    schedule 18.12.2018

Это называется диспетчером контекста, и я просто хочу добавить, что аналогичные подходы существуют и для других языков программирования. Их сравнение может быть полезно для понимания диспетчера контекста в Python. По сути, диспетчер контекста используется, когда мы имеем дело с некоторыми ресурсами (файлом, сетью, базой данных), которые необходимо инициализировать и в какой-то момент удалить (удалить). В Java 7 и более поздних версиях у нас есть автоматическое управление ресурсами, которое принимает форму:

//Java code
try (Session session = new Session())
{
  // do stuff
}

Обратите внимание, что Session необходимо реализовать AutoClosable или один из его (многих) подчиненных интерфейсов.

В C # мы используем операторы для управления ресурсами, которые имеют форму:

//C# code
using(Session session = new Session())
{
  ... do stuff.
}

В котором Session следует реализовать IDisposable.

В python используемый нами класс должен реализовывать __enter__ и __exit__. Итак, это принимает форму:

#Python code
with Session() as session:
    #do stuff

И, как указывали другие, вы всегда можете использовать оператор try / finally на всех языках для реализации одного и того же механизма. Это просто синтаксический сахар.

person Rohola Zandie    schedule 20.02.2020

Python вызывает __enter__, когда выполнение входит в контекст оператора with и пора получить ресурс. Когда выполнение снова выходит из контекста, 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')`enter code here`

Python вызывает enter, когда выполнение входит в контекст оператора with и пора получить ресурс. Когда выполнение снова выходит из контекста, Python вызывает exit, чтобы освободить ресурс.

Написание диспетчера контекста на основе классов - не единственный способ поддержать оператор with в Python. Утилита contextlib в стандартной библиотеке предоставляет еще несколько абстракций, построенных на основе базового протокола диспетчера контекста. Это может немного облегчить вашу жизнь, если ваши варианты использования соответствуют тому, что предлагает contextlib.

person Viraj Dhanushka    schedule 21.11.2020