Пояснення '__enter__' та '__exit__' Python


361

Я бачив це в чиєсь коді. Що це означає?

    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

19
Хороше пояснення тут: effbot.org/zone/python-with-statement.htm
Манур

7
@StevenVascellaro Редагування коду питання, як правило, погана ідея, особливо коли в коді є помилки. Це запитання було задано з урахуванням Py2, і немає жодної причини оновити його на Py3.
jpaugh

Відповіді:


420

Використання цих магічних методів ( __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 - "З" заявою " також є хороша написання.


20
Ймовірно, __enter__слід повертатися selfзавжди, оскільки тоді в контексті можна викликати лише інші методи класу.
ViFI

3
@ViFI У def __enter__(self)PEP 343 є 4 приклади, і ніхто цього не робить return self: python.org/dev/peps/pep-0343 . Чому ти так думаєш?
Горе

4
@Grief: По 2 причинам, по - моєму, 1) я не буду мати можливість викликати інші методи selfоб'єкта , як описано тут: stackoverflow.com/questions/38281853 / ... 2) self.XYZ є лише частиною власного об'єкта і повернення ручки тільки для того, що здається мені недоцільним з точки зору обслуговування. Я вважаю за краще повернути обробку для завершення об'єкта, а потім надати публічні API лише тим selfоб’єктам компонентів , які я хочу піддати користувачеві, як в with open(abc.txt, 'r') as fin: content = fin.read()
ViFI

4
Об'єкти файлу повертаються selfзвідти __enter__, і саме так ви можете обробити файл як fвсерединіwith open(...) as f
holdenweb

2
Я повинен був зрозуміти одну тонкощі: якщо об'єкт вимагає параметрів для ініціалізації, вони повинні бути в init , а не в self .
dfrankow

70

Якщо ви знаєте, що таке контекстні менеджери , то вам більше нічого не потрібно для розуміння __enter__та __exit__магічних методів. Давайте подивимось дуже простий приклад.

У цьому прикладі я відкриваю myfile.txt за допомогою відкритої функції. Блок спробувати / нарешті гарантує, що навіть у випадку несподіваного винятку 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) 

Якщо ви подивитеся на код, я не закрив файл, і немає спроби / нарешті блокувати. Тому що з заявою автоматично закривається myfile.txt . Ви навіть можете перевірити це, зателефонувавши print(fp.closed)атрибут - який повертається True.

Це тому, що файлові об’єкти (fp у моєму прикладі), повернені відкритою функцією, мають два вбудовані методи __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__магічних методів.


53

Мені було дивно важко знайти документи 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прив'язує повернене значення цього методу до цільових цілей, зазначених у підпункті твердження, якщо такі є.

object.__exit__(self, exc_type, exc_value, traceback)
Вийдіть із контексту виконання цього об'єкта. Параметри описують виняток, який спричинив вихід контексту. Якщо контекст вийшов без винятку, всі три аргументи будуть None.

Якщо надається виняток, і метод хоче придушити виняток (тобто запобігти його поширенню), він повинен повернути справжнє значення. В іншому випадку виняток буде оброблятися нормально при виході з цього методу.

Зауважте, що __exit__()методи не повинні переоцінювати винятий, що передається; це відповідальність за абонента.

Я сподівався на чіткий опис __exit__аргументів методу. Цього не вистачає, але ми можемо їх вивести ...

Імовірно, exc_typeце клас винятку.

У ньому сказано, що не слід повторно піднімати виняток, що передається. Це говорить нам про те, що одним із аргументів може бути фактичний екземпляр винятку ... або, можливо, ви повинні приміркувати його самостійно від типу та значення?

Ми можемо відповісти, переглянувши цю статтю:
http://effbot.org/zone/python-with-statement.htm

Наприклад, наступний __exit__метод проковтує будь-який TypeError, але дозволяє отримати всі інші винятки через:

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

... так чітко valueє екземпляром винятку.

І, мабуть, tracebackє об'єктом простеження Python .


2
Погодьтеся. Цей URL так важко знайти.
Шихао Сюй

може бути важливо відзначити цей похований біт у довіднику PEP, зазначаючи використання arg: python.org/dev/peps/pep-0343/#generator-decorator
Tcll

43

На додаток до вищезазначених відповідей для прикладу порядку виклику, простий приклад запуску

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

3
І навіть якщо послідовність визначень переключена, порядок виконання залишається таким же!
Шон

1
Це було дуже корисно. Дякую.
Reez0

5

спробуйте додати мої відповіді (думка про навчання):

__enter__і [__exit__]обидва - це методи, які викликаються при вході та виході з тіла " із заявою " ( PEP 343 ) та реалізація обох називається менеджером контексту.

Оператор 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

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

вибачте за мою погану англійську мову та мої незрозумілі пояснення, дякую ....


1

Це називається контекстним менеджером, і я просто хочу додати, що подібні підходи існують і для інших мов програмування. Порівняння їх може бути корисним для розуміння менеджера контексту в python. В основному, контекстний менеджер використовується, коли ми маємо справу з деякими ресурсами (файл, мережа, база даних), які потрібно ініціалізувати і в якийсь момент зірвати (розмістити). У Java 7 і вище ми маємо автоматичне управління ресурсами у формі:

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

Зауважте, що сесії потрібно реалізувати AutoClosableабо один із (безлічі) підінтерфейсів.

У C # ми використовуємо оператори для управління ресурсами у формі:

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

У якому Sessionслід реалізувати IDisposable.

У python клас, який ми використовуємо, повинен реалізувати __enter__та __exit__. Отже, вона має форму:

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

І як зазначали інші, ви завжди можете використовувати спробу / остаточне твердження на всіх мовах, щоб реалізувати той самий механізм. Це просто синтаксичний цукор.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.