为什么__del__在with块结束时会被调用?

11

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__(在上面的#第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__方法?另外,如果第二个with块中调用了连接的__del__方法,我不明白为什么my_curs1.connection之后仍然处于打开状态(请参见上面的# line 2)。

2
Chengcheng,关于你的第二个问题,请参考@tzaman提供的链接。请从你的问题中删除该部分。将一个问题限定在一个主题范围内有助于保持StackOverflow的整洁,并使人们更快地找到答案。谢谢! - CodeMouse92
1
@tzaman 那个问题已经有3年历史了,而且它的答案是错误的。 - Air
真的 - 这是一个有趣的问题。这里有一个更加实时的关于mysqldb上下文管理器的讨论 here。@air是正确的 - 链接的重复已经过时了。 - J Richard Snape
@JRichardSnape 这篇文章与我的不同。我知道为什么游标没有关闭。因为with语句不会调用close()方法。在这里可以看到https://dev59.com/b2035IYBdhLWcg3wBLRL - BAE
@ChengchengPei 是的 - 我知道这是不同的。我试图防止你的问题被关闭为重复,就像@tzaman建议的那样,因为那里的答案已经过时了。直到15分钟前,我才不知道你提供的链接。我只能猜测为什么会调用__del__,但我可以告诉你它在Python聊天室中引发了一些讨论 - http://chat.stackoverflow.com/rooms/6/python - J Richard Snape
1个回答

7

需要注意的是,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 语句会调用 exit() 和 enter() 方法,但不会调用 close() 方法。请参见 https://dev59.com/b2035IYBdhLWcg3wBLRL。 - BAE
我只是想知道为什么在打印语句(我帖子中的第一行)之前调用 del()。如果在程序退出时调用 del(),那么对我来说就有意义了。 - BAE
__del__ 被调用是因为 with 块构造的临时对象没有更多的引用。请记住,foo 不是你创建的 Foo() - QuestionC
现在我明白了,如果Python在程序退出之前进行垃圾回收,那就有意义了。谢谢。 - BAE
从技术上讲,它可以做任何想做的事情,但实际上,是的,只要您不再引用对象,它就会清理干净。 - QuestionC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接