关于oop:解释Python的’__enter__’和’__exit__’

Explaining Python's '__enter__' and '__exit__'

我在别人的密码里看到了这个。这是什么意思?

1
2
3
4
5
    def __enter__(self):
        return self

    def __exit__(self, type, value, tb):
        self.stream.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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


使用这些神奇的方法(__enter____exit__允许您实现可与with语句轻松使用的对象。

其思想是,它使构建需要执行一些"清除"代码的代码变得容易(将其视为一个try-finally块)。这里有更多的解释。

一个有用的例子可以是数据库连接对象(一旦相应的'with'-语句超出范围,它就会自动关闭连接):

1
2
3
4
5
6
7
8
9
10
11
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语句一起使用(如果您使用的是python 2.5,则可能需要在文件顶部执行from __future__ import with_statement)。

1
2
with DatabaseConnection() as mydbconn:
    # do stuff

pep343——"with"语句也有很好的描述。


如果您知道上下文管理器是什么,那么您就不需要更多了解__enter____exit__的神奇方法。让我们看一个非常简单的例子。

在这个例子中,我在open函数的帮助下打开myfile.txt。try/finally块确保即使发生意外异常,myfile.txt也将关闭。

1
2
3
4
5
6
fp=open(r"C:\Users\SharpEl\Desktop\myfile.txt")
try:
    for line in fp:
        print(line)
finally:
    fp.close()

现在我打开的是同一个带有语句的文件:

1
2
3
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

这是因为open函数返回的文件对象(在我的示例中是fp)有两个内置方法__enter____exit__。它也被称为上下文管理器。在WITH块的开头调用__enter__方法,在结尾调用__exit__方法。注:WITH语句仅适用于支持上下文管理协议的对象,即它们具有__enter____exit__方法。实现这两种方法的类称为上下文管理器类。

现在让我们定义自己的上下文管理器类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 class Log:
    def __init__(self,filename):
        self.filename=filename
        self.fp=None    
    def logging(self,text):
        self.fp.write(text+'
'
)
    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__魔法方法有了基本的了解。


我发现很难通过google找到__enter____exit__方法的python文档,因此这里的链接是帮助其他人:

https://docs.python.org/2/reference/datamodel.html带语句上下文管理器https://docs.python.org/3/reference/datamodel.html带语句上下文管理器(两个版本的细节相同)

object.__enter__(self)
Enter the runtime context related to this object. The with statement will bind this method’s return value to the target(s) specified in the as clause of the statement, if any.

object.__exit__(self, exc_type, exc_value, traceback)
Exit the runtime context related to this object. The parameters describe the exception that caused the context to be exited. If the context was exited without an exception, all three arguments will be None.

If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a true value. Otherwise, the exception will be processed normally upon exit from this method.

Note that __exit__() methods should not reraise the passed-in exception; this is the caller’s responsibility.

我希望能对__exit__方法参数有一个清晰的描述。这是缺乏的,但我们可以推断出…

大概exc_type是例外的一类。

它说你不应该再提高通过的例外。这向我们表明,其中一个参数可能是实际的异常实例……或者您应该自己从类型和值实例化它?

我们可以通过阅读本文来回答:http://effbot.org/zone/python-with-statement.htm网站

For example, the following __exit__ method swallows any TypeError, but lets all other exceptions through:

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

…很明显,value是一个例外情况。

大概traceback是一个python回溯对象。


除了上面的示例调用顺序的答案之外,一个简单的运行示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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")

产生输出:

1
2
3
4
5
__init__
__enter__
body
__exit__
__del__

提示:当使用语法with myclass() as mc时,变量mc得到__enter__()返回的值,在上述情况下,None返回的值!为此,需要定义返回值,例如:

1
2
3
def __enter__(self):
    print('__enter__')
    return self


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

__enter__[__exit__]都是在进入和退出"with语句"(pep 343)主体时调用的方法,它们的实现称为上下文管理器。

WITH语句旨在隐藏try finally子句的流控制,并使代码不可破解。

WITH语句的语法为:

1
2
with EXPR as VAR:
    BLOCK

翻译为(如PEP 343所述):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)

尝试一些代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
>>> 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'

现在尝试手动(按照翻译语法):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> 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

服务器端的结果与以前相同

对不起,我的英语不好,解释不清,谢谢……