解释Python的'__enter __'和'__exit__'

zjm*_*126 314 python oop with-statement

我在某人的代码中看到了这个.这是什么意思?

    def __enter__(self):
        return self

    def __exit__(self, type, value, tb):
        self.stream.close()
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)

Chr*_*heD 372

使用这些魔术方法(__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()
        ...
Run Code Online (Sandbox Code Playgroud)

如上所述,将此对象与with语句一起使用(from __future__ import with_statement如果您使用的是Python 2.5,则可能需要在文件的顶部执行此操作).

with DatabaseConnection() as mydbconn:
    # do stuff
Run Code Online (Sandbox Code Playgroud)

PEP343 - 'with'语句'也有一个很好的写法.

  • 也许,`__ enter __`应该始终返回`self`,因为只能在上下文中调用类的其他方法. (16认同)
  • @Grief:有两个原因,在我看来,1)我将无法在"self"对象上调用其他方法,如下所述:http://stackoverflow.com/questions/38281853/should-the-return- value-for-enter-method-always-be-self-in-python 2)self.XYZ只是self对象的一部分,从维护的角度来看,只返回那个对我来说不合适的句柄.我宁愿返回句柄来完成对象,然后只向那些组件`self`对象提供公共API,我希望向用户公开,例如`with open(abc.txt,'r')作为fin:content = fin.read()` (4认同)
  • 文件对象从`__enter__`返回`self`,这就是为什么你可以将文件处理为`f`里面`with open(...)as f` (4认同)
  • @ViFI在PEP 343中有4个`def __enter __(self)`的例子,没有人'返回自我':https://www.python.org/dev/peps/pep-0343/.你为什么这么认为? (3认同)
  • IMO 返回的内容应该由类的开发人员决定,只要记录的用法对于类的用户来说似乎是合乎逻辑的: 1. 打开一个不应该保持打开状态的资源2. 使用资源,调用资源上的某些方法等 3. 资源被关闭 (3认同)
  • 我必须理解的一个微妙之处:如果对象需要参数初始化,那些应该在__init__上,而不是__self__. (2认同)

N R*_*awa 56

如果你知道什么是上下文管理器,那么你就不需要了解__enter____exit__魔术方法.让我们看一个非常简单的例子.

在这个例子中,我在open函数的帮助下打开myfile.txt.在尝试/终于块确保即使发生意外的异常myfile.txt的将被关闭.

fp=open(r"C:\Users\SharpEl\Desktop\myfile.txt")
try:
    for line in fp:
        print(line)
finally:
    fp.close()
Run Code Online (Sandbox Code Playgroud)

现在我用with语句打开同一个文件:

with open(r"C:\Users\SharpEl\Desktop\myfile.txt") as fp:
    for line in fp:
        print(line) 
Run Code Online (Sandbox Code Playgroud)

如果你看代码,我没有关闭文件,没有try/finally块.因为with语句会自动关闭 myfile.txt.您甚至可以通过调用print(fp.closed)属性来检查它- 返回True.

这是因为open函数返回的文件对象(在我的例子中为fp)有两个内置方法__enter____exit__.它也被称为上下文管理器.__enter__方法被称为在开始块和__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")
Run Code Online (Sandbox Code Playgroud)

现在我希望你有两个基本的了解__enter____exit__魔术方法.


Ane*_*pic 44

我发现通过Googling 找到python文档__enter____exit__方法很奇怪,所以在这里帮助其他人是链接:

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)
Run Code Online (Sandbox Code Playgroud)

......显然value是一个异常实例.

并且可能traceback是Python 回溯对象.

  • 同意。这个网址很难找到。 (2认同)
  • FWIW, \`__exit__` 方法参数似乎是 [sys.exc_info()](https://docs.python.org/3/library/sys.html#sys.exc_info) 的输出元组 (2认同)

Yur*_*man 31

除了举例说明调用顺序的上述答案外,还有一个简单的运行示例

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")
Run Code Online (Sandbox Code Playgroud)

产生输出:

__init__
__enter__
body
__exit__
__del__
Run Code Online (Sandbox Code Playgroud)

提醒一下:在使用语法时with myclass() as mc,变量mc获取返回的值__enter__(),在上面的例子中None!对于这种用法,需要定义返回值,例如:

def __enter__(self): 
    print('__enter__')
    return self
Run Code Online (Sandbox Code Playgroud)

  • 即使切换了定义的顺序,执行顺序也保持不变! (3认同)

Roh*_*die 6

这称为上下文管理器,我只想补充一点,其他编程语言也存在类似的方法。比较它们可能有助于理解 python 中的上下文管理器。基本上,当我们处理一些需要初始化并在某些时候拆除(处置)的资源(文件、网络、数据库)时,会使用上下文管理器。在Java 7及更高版本中,我们有采用以下形式的自动资源管理:

//Java code
try (Session session = new Session())
{
  // do stuff
}
Run Code Online (Sandbox Code Playgroud)

请注意, Session 需要实现AutoClosable或其(许多)子接口之一。

C# 中,我们使用 using 语句来管理采用以下形式的资源:

//C# code
using(Session session = new Session())
{
  ... do stuff.
}
Run Code Online (Sandbox Code Playgroud)

Session应在其中实施IDisposable.

python 中,我们使用的类应该实现__enter____exit__。所以它采用以下形式:

#Python code
with Session() as session:
    #do stuff
Run Code Online (Sandbox Code Playgroud)

正如其他人指出的那样,您始终可以在所有语言中使用 try/finally 语句来实现相同的机制。这只是语法糖。


Wir*_*kti 5

尝试添加我的答案(我的学习想法):

__enter__[__exit__]这两个是被调用的进入和退出“的体方法在with语句”(PEP 343)和执行都被称为上下文管理。

with 语句旨在隐藏 try finally 子句的流程控制并使代码变得难以理解。

with 语句的语法是:

with EXPR as VAR:
    BLOCK
Run Code Online (Sandbox Code Playgroud)

转换为(如 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)
Run Code Online (Sandbox Code Playgroud)

尝试一些代码:

>>> 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'
Run Code Online (Sandbox Code Playgroud)

现在手动尝试(遵循翻译语法):

>>> 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
Run Code Online (Sandbox Code Playgroud)

服务器端的结果和之前一样

抱歉我的英语不好和解释不清楚,谢谢....