cppython
cppython

Reputation: 1279

understand closure in python

i try to understand closure by the following codes

main

class foo(object):
      def __init__(self):
          self.a=10
      def test(self):
          def re():
              print self.a
          return re        


f1=foo()
k1=f1.test()
k1()#               output 10
f1.a=20#            line A
k1()#               output 20

appends case1 or case2

case1:

del f1#             line B
k1()#               line C output 20

case2:

del f1.a
k1()#               line D .error

from line A, it seems k1() output depends on f1.a. if i deleted f1, f1.a should be removed. why does lineC output 20? . if k1() does not depend on f1.a , why does lineD output error? please help to explain . thanks

Upvotes: 0

Views: 71

Answers (2)

Anton Zuenko
Anton Zuenko

Reputation: 761

if i deleted f1, f1.a should be removed

That's not correct. if you del f1, it decreases reference counter of f1 object and removes it from local dict so it becomes inaccessible. When ref. counter reaches 0, Python GC returns occupied memory to the OS.

When you create closures that access outer context, Python implicitly creates references to that context in local closure scope. Add print(locals()) before print self.a to see that foo object is referenced in k1.

So when you delete f1 in case 1 with existing k1, f1 disappears from module's context, but the object still exists with a single reference in k1. So it's ok to call k1() and get 20.

When you del f1.a in case 2, it deletes a from the context of f1 object. There's only one object f1 so a becomes inaccessible neither from f1.a nor from k1.

Upvotes: 1

BrenBarn
BrenBarn

Reputation: 251383

It is not totally accurate to say "k1 depends on f1.a". k1 makes use of the expression self.a, and self.a is not an atomic unit, but is made of a bare name (self) and an attribute (a). Either one of these can fail independently. In the second case, it is only the attribute lookup that fails (i.e., the object does not have an attribute a).

By doing del f1, you did not delete the object. You only deleted the name f1. If there are other references to that object, the object will still exist. And there is another reference, namely in the closure of k1. See this question for more explanation, but basically when you call f1.test(), the value of self for that call is "saved" within the returned function (what you called k1), because self is a local variable of the enclosing function test. Thus k1 still has access to the object formerly known as f1, and the call succeeds.

In your second case, you deleted the attribute a from the object known as f1. re does not save a reference to that attribute, because closures only save local variables of the enclosing function. When you call re, it tries to look up self.a, and this fails because, although the object exists, the attribute isn't there.

One thing that may be helpful in understanding the difference: notice that re does not make use of the name f1. It refers to the name self. In your setup, this winds up being the same object as f1, but the object has two different names pointing at it. In contrast, re does make use of the attribute name a, and it is that usage which fails when it's called in your Case 2.

If re did use the name f1, it would also fail if that name were deleted. If you change re to do print f1.a instead of print self.a, then it will be looking for a global variable with the name f1, and will fail if that variable does not exist. In this case, it never gets to try looking for an attribute a, since the object doesn't exist in the first place.

Upvotes: 2

Related Questions