为啥 __del__ 在 with 块的末尾被调用?

Posted

技术标签:

【中文标题】为啥 __del__ 在 with 块的末尾被调用?【英文标题】:Why is __del__ called at the end of a with block?为什么 __del__ 在 with 块的末尾被调用? 【发布时间】:2016-01-03 16:04:51 【问题描述】:

with 语句中创建的变量的范围在with 块之外(参考:Variable defined with with-statement available outside of with-block?)。但是当我运行以下代码时:

class Foo:
    def __init__(self):
        print "__int__() called."

    def __del__(self):
        print "__del__() called."

    def __enter__(self):
        print "__enter__() called."
        return "returned_test_str"

    def __exit__(self, exc, value, tb):
        print "__exit__() called."

    def close(self):
        print "close() called."

    def test(self):
        print "test() called."

if __name__ == "__main__":
    with Foo() as foo:
        print "with block begin???"
        print "with block end???"

    print "foo:", foo  # line 1

    print "-------- Testing mysqldb -----------------------"
    with MySQLdb.Connect(host="xxxx", port=0, user="xxx", passwd="xxx", db="test") as my_curs2:
        print "(1)my_curs2:", my_curs2
        print "(1)my_curs2.connection:", my_curs2.connection
    print "(2)my_curs2.connection:", my_curs2.connection
    print "(2)my_curs2.connection.open:", my_curs2.connection.open  # line 2

输出显示在打印 foo 之前调用了 Foo.__del__(在上面的 # line 1):

__int__() called.
__enter__() called.
with block begin???
with block end???
__exit__() called.
__del__() called.
foo: returned_test_str
-------- Testing MySQLdb -----------------------
(1)my_curs2: <MySQLdb.cursors.Cursor object at 0x7f16dc95b290>
(1)my_curs2.connection: <_mysql.connection open to 'xxx' at 2609870>
(2)my_curs2.connection: <_mysql.connection open to 'xxx' at 2609870>
(2)my_curs2.connection.open: 1

我的问题是,如果with 语句没有创建新的执行范围,为什么在这里调用Foo.__del__

另外,如果连接的__del__ 方法在第二个with 块中被调用,我不明白为什么my_curs1.connection 之后仍然打开(参见上面的# line 2)。

【问题讨论】:

Can I use with statement with MySQLdb.Connection object?的可能重复 Chengcheng,请参阅@tzaman 的链接以获取第二个问题的答案,并从您的问题中删除该部分。一个问题对一个问题有助于保持 *** 的整洁,并使人们能够更快地找到答案。谢谢! @tzaman 这个问题有 3 年历史了,它的答案不正确。 是的 - 这是一个有趣的问题。 mysqldb 上下文管理器 here 有更新的讨论。 @air 是对的 - 链接的副本已过期 @JRichardSnape 该帖子与我的不同。我知道为什么游标没有关闭。因为 with 语句不调用 close()。见这里***.com/questions/5669878/… 【参考方案1】:

请务必注意 foo 不是 Foo 类型的对象。您确实创建了一个Foo 并需要保留它,因为它可能包含调用__exit__ 所需的状态信息。但一旦完成,对象就不再需要,Python 可以随意丢弃它。

换一种说法,就是这样:

with Foo() as foo:
    print ('Hello World!')

和这个是一样的:

_bar = Foo()
foo = _bar.__enter__()
print ('Hello World!')
_bar.__exit__()
del _bar # This will call __del__ because _bar is the only reference

如果foo 是对with 块的foo 的引用,您所期望的行为将会发生。比如……

class Foo:
    def __init__(self):
        print ("__int__() called.")

    def __del__(self):
        print ("__del__() called.")

    def __enter__(self):
        print ("__enter__() called.")
        return self # foo now stores the Foo() object

    def __str__(self):
        return 'returned_test_str'

    def __exit__(self, exc, value, tb):
        print ("__exit__() called.")

    def close(self):
        print ("close() called.")

    def test(self):
        print ("test() called.")

if __name__ == "__main__":
    with Foo() as foo:
        print ("with block begin???")
        print ("with block end???")

    print ("foo:", foo)  # line 1

打印

__int__() called.
__enter__() called.
with block begin???
with block end???
__exit__() called.
foo: returned_test_str
__del__() called.

我不知道为什么 Connection.__exit__ 会保持光标打开。

【讨论】:

"with closing(self.__conn.cursor()) as cur:"" 会调用 close(),但不会调用 __exit__() 和 __enter__()。但是 with-statement 会调用 __exit__()和 __enter__(),但不是 close()。请参阅***.com/questions/5669878/… 我只是想知道为什么 __del__() 在 print 语句(我的帖子中的第 1 行)之前被调用。如果在程序退出时调用 __del__() 对我来说是有意义的。 __del__ 被调用,因为没有更多对 with 块构造的临时对象的引用。请记住,foo 不是您创建的 Foo() 如果python在程序退出之前执行gc,这对我来说很有意义。谢谢。 从技术上讲,它可以做它想做的事,但实际上是的,它会在你没有更多对对象的引用时立即清理。

以上是关于为啥 __del__ 在 with 块的末尾被调用?的主要内容,如果未能解决你的问题,请参考以下文章

83.魔法方法__del__()

83.魔法方法__del__()

python 关键字 del 用法

类的析构方法__del__

_del_()方法

Python __del__方法:销毁对象 垃圾回收机制