解释Python的'__enter__'和'__exit__

582 投票
7 回答
496634 浏览
提问于 2025-04-15 17:30

我在某人的代码里看到了这个。它是什么意思呢?

    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 个回答

100

我发现通过谷歌搜索,找到关于 __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 会给出默认的异常传播行为)。

140

如果你知道什么是上下文管理器,那么理解__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__这两个神奇的方法有了基本的理解。

654

使用这些神奇的方法(__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' 也有很好的介绍。

撰写回答