理解python中的闭包

时间:2014-11-30 19:50:43

标签: python closures

我尝试通过以下代码了解结束

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

附加case1或case2

情形1:

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

情况2:

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

从A行看来,k1()输出似乎取决于f1.a.如果我删除了f1,则应删除f1.a。为什么lineC输出20? 。如果k1()不依赖于f1.a,为什么lineD输出错误?请帮忙解释一下。感谢

2 个答案:

答案 0 :(得分:2)

说" k1取决于f1.a"并不完全准确。 k1使用表达式self.aself.a不是原子单位,而是由一个裸名称(self)和一个属性(a)组成。其中任何一个都可以独立失败。在第二种情况下,只有属性查找失败(即,对象没有属性a)。

通过执行del f1,您没有删除该对象。您只删除了名称 f1。如果存在对该对象的其他引用,则该对象仍将存在。还有另一个参考,即k1的结束。有关详细说明,请参阅this question,但基本上在您致电f1.test()时,该通话的self值为"已保存"在返回的函数(你称之为k1)中,因为self是封闭函数test的局部变量。因此,k1仍然可以访问以前称为f1的对象,并且调用成功。

在第二种情况下,您从名为a的对象中删除了属性f1re不保存对该属性的引用,因为闭包只保存封闭函数的局部变量。当您致电re时,它会尝试查找self.a,但这会失败,因为虽然该对象存在,但该属性不存在。

有一点可能有助于理解差异:请注意re未使用名称 f1。它指的是名称self。在您的设置中,它最终与f1成为同一个对象,但该对象有两个不同的名称指向它。相反,re 使用属性名称a,并且在您的案例2中调用时,该用法会失败。

如果re 使用名称f1,则删除该名称时也会失败。如果您将re更改为print f1.a而不是print self.a,那么它将查找名为f1的全局变量,如果该变量不存在则会失败。在这种情况下,它永远不会尝试寻找属性a,因为该对象首先不存在。

答案 1 :(得分:1)

如果我删除了f1,则应删除f1.a

这不正确。如果您del f1,它会减少f1对象的引用计数器,并将其从本地 dict 中删除,以使其无法访问。什么时候参考计数器达到0,Python GC将占用的内存返回给OS。

当您创建访问外部上下文的闭包时,Python会在本地闭包范围内隐式创建对该上下文的引用。在print(locals())之前添加print self.a以查看在k1中引用了foo对象。

因此,如果您在现有f1的案例1中删除k1f1将从模块的上下文中消失,但该对象仍然存在且{{1}中有一个引用}}。所以可以拨打k1并获得20。

在案例2中k1()时,它会从del f1.a对象的上下文中删除a。只有一个对象f1,f1无法从af1.a无法访问。