11

The scope of the variables created in a with statement is outside the with block (refer: Variable defined with with-statement available outside of with-block?). But when I run the following code:

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

The output shows that Foo.__del__ is called before printing foo (at # line 1 above):

__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

My question is, why is Foo.__del__ called here, if the with statement does not create a new execution scope?

Also, if the connection's __del__ method is called in the second with block, I don't understand why my_curs1.connection is still open afterward (see # line 2 above).

Community
  • 1
  • 1
BAE
  • 8,550
  • 22
  • 88
  • 171
  • 1
    Possible duplicate of [Can I use with statement with MySQLdb.Connection object?](http://stackoverflow.com/questions/11751703/can-i-use-with-statement-with-mysqldb-connection-object) – tzaman Oct 06 '15 at 16:47
  • 2
    Chengcheng, see @tzaman's link for the answer to your second issue, and remove that part from your question. Keeping one issue to one question helps keep StackOverflow tidy and enables people to find answers more quickly. Thank you! – CodeMouse92 Oct 06 '15 at 17:36
  • 1
    @tzaman That question is 3 years old and its answer is incorrect. – Air Oct 06 '15 at 17:51
  • True - this is an interesting question. There's a more up to date discussion of `mysqldb` context manager [here](http://stackoverflow.com/questions/31374857/why-doesnt-the-mysqldb-connection-context-manager-close-the-cursor). @air is right - the linked duplicate is out of date – J Richard Snape Oct 06 '15 at 18:39
  • @JRichardSnape The post is different from mine. I know why cursor is not closed. because with-statement does not call close(). See here http://stackoverflow.com/questions/5669878/when-to-close-cursors-using-mysqldb – BAE Oct 06 '15 at 18:49
  • @ChengchengPei Yes - I know it's different. I was trying to prevent your question getting closed as a duplicated of as suggest by @tzaman, because the answer there is out of date. I was unaware of the link you provided until 15 mins ago. I can only guess at why `__del__` is called, but I can tell you it's prompting some discussion in the Python chatroom - http://chat.stackoverflow.com/rooms/6/python – J Richard Snape Oct 06 '15 at 18:56

1 Answers1

7

It's important to note that foo is not an object of type Foo. You do create a Foo and need to keep it around because it might contain state information needed to call __exit__. But once that's done, the object is unneeded and Python's free to throw it away.

Put another way, this:

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

Is the same as this:

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

The behavior you are expecting would happen if foo were a reference to the with block's foo. For example...

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

Prints

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

I have no idea why Connection.__exit__ would leave its cursors open however.

QuestionC
  • 10,006
  • 4
  • 26
  • 44
  • "with closing( self.__conn.cursor() ) as cur:"" will call close(), but not __exit__() and __enter__(). But with-statement will call __exit__() and __enter__(), but not close(). Please see http://stackoverflow.com/questions/5669878/when-to-close-cursors-using-mysqldb – BAE Oct 06 '15 at 19:10
  • I just wonder why __del__() is called before print statement (line1 in my post). I make sense to me if __del__() is called when program exit. – BAE Oct 06 '15 at 19:12
  • `__del__` is being called because there are no more references to the temporary object constructed by the `with` block. Remember, `foo` is not the `Foo()` you made. – QuestionC Oct 06 '15 at 19:15
  • That makes sense to me now if python does gc before program exits. Thanks. – BAE Oct 06 '15 at 19:17
  • Technically it can do what it wants, but in practice yea it cleans up as soon as you don't have any more references to an object. – QuestionC Oct 06 '15 at 19:20