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

时间:2009-12-31 07:07:19

标签: python oop with-statement

我在某人的代码中看到了这一点。这是什么意思?

    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

7 个答案:

答案 0 :(得分:329)

使用这些魔术方法(__enter____exit__)可以实现可以使用with语句轻松使用的对象。

这个想法是,它可以很容易地构建需要执行一些“清除”代码的代码(将其视为try-finally块)。 Some more explanation here

一个有用的例子可能是数据库连接对象(一旦相应的'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语句一起使用(如果您使用的是Python 2.5,则可能需要在文件顶部执行from __future__ import with_statement。)

with DatabaseConnection() as mydbconn:
    # do stuff

PEP343 -- The 'with' statement'也有很好的写作。

答案 1 :(得分:48)

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

在此示例中,我在打开功能的帮助下打开 myfile.txt 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 块。因为 with 语句会自动关闭 myfile.txt 。您甚至可以通过调用print(fp.closed)属性来检查它,该属性会返回True

这是因为打开函数返回的文件对象(我的示例中为fp)有两个内置方法__enter____exit__。它也被称为上下文管理器。 {<1}}方法在开头使用块调用,最后调用__enter__方法。注意: with 语句仅适用于支持上下文管理协议的对象,即它们具有__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__魔术方法有基本的了解。

答案 2 :(得分:36)

我发现通过谷歌搜索找到__enter____exit__方法的python文档非常困难,所以在这里帮助其他人是链接:

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 traceback对象。

答案 3 :(得分:27)

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

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

答案 4 :(得分:3)

这称为上下文管理器,我只想补充一下,其他编程语言也存在类似的方法。比较它们可能有助于理解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

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

答案 5 :(得分:2)

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

__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

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

对不起,我的英语不好,我的解释不清楚,谢谢。...

答案 6 :(得分:1)

当执行进入with语句的上下文并且需要获取资源时,Python会调用__enter__。当执行再次离开上下文时,Python调用__exit__释放资源

让我们考虑一下上下文管理器和Python中的“ with”语句。上下文管理器是对象需要遵循的简单“协议”(或接口),因此可以与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`
当执行进入with语句的上下文并且需要获取资源时,

Python调用 enter 。当执行再次离开上下文时,Python调用 exit 释放资源。

编写基于类的上下文管理器并不是在Python中支持with语句的唯一方法。标准库中的contextlib实用程序模块提供了一些在基本上下文管理器协议之上构建的抽象。如果您的用例与contextlib提供的相匹配,则可以使您的生活更轻松。