解释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
7 个回答
我发现通过谷歌搜索,找到关于 __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
是异常的类型。
文中提到不应该重新抛出传入的异常。这让我们猜测其中一个参数可能是一个实际的异常实例……或者也许你应该自己根据类型和数值来实例化它?
我们可以通过查看这篇文章来找到答案:
http://effbot.org/zone/python-with-statement.htm
例如,下面的
__exit__
方法会吞掉任何 TypeError,但会让其他所有异常通过:
def __exit__(self, type, value, traceback):
return isinstance(value, TypeError)
……所以很明显 value
是一个异常实例。
而 traceback
可能是一个 Python 的 traceback 对象。
注意这里还有更多文档:
https://docs.python.org/3/library/stdtypes.html#context-manager-types
……它们对 __enter__
和 __exit__
方法有稍微详细的解释。特别是更明确地指出 __exit__
应该返回一个布尔值(虽然真值或假值都可以,比如隐式返回 None
会给出默认的异常传播行为)。
如果你知道什么是上下文管理器,那么理解__enter__
和__exit__
这两个神奇的方法就不成问题了。我们来看一个非常简单的例子。
在这个例子中,我使用open函数打开了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语句打开同一个文件:
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__
。这也被称为上下文管理器。__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__
这两个神奇的方法有了基本的理解。
使用这些神奇的方法(__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
语句(如果你在使用 Python 2.5,可能需要在文件顶部加上 from __future__ import with_statement
)。
with DatabaseConnection() as mydbconn:
# do stuff
PEP343 -- The 'with' statement' 也有很好的介绍。