python中的闭包捕获对象引用,还是只需要它们需要的属性?

时间:2013-02-20 16:38:17

标签: python closures

这个问题是关于python如何智能地进行逃逸分析。

假设我有以下计划:

class Dog():
  breed = 'electronic dog'
  collar_type = 'microsoft'

sparky=Dog()
def get_dog_info():
  return sparky.breed

函数get_dog_info()显然必须关闭sparky.breed。但是,为此,实现是否也会逃避整个Dog对象?也就是说,关闭collar_type会有额外的内存成本吗?或者这是一个留给实施的选择?

3 个答案:

答案 0 :(得分:3)

构成模块的全局命名空间引用Dogsparky,并将它们保存在内存中。

如果您要运行del Dogsparky仍然会引用该类(通过它的__class__引用)来保持它的存活。该类引用了属于它定义的两个属性,因此它们也保持活着。这是get_dog_info函数的所有独立

CPython根据引用计数将对象保存在内存中;如果Python中的任何内容开始引用对象 somethere ,则该对象的引用计数增加1,并在删除引用时再次减少。当计数降至0时,对象将从内存中删除,并且垃圾收集过程会根据需要拆分循环引用以促进此过程。

请注意,因为sparky是全局的,所以函数代码不会直接引用任何内容;在运行时查找全局变量。如果您也要删除sparky,则会清除所有引用。由于sparky中的get_dog_info()在全局命名空间中被查找,因此调用get_dog_info()会产生NameError

如果确实有一个闭包(引用父函数作用域中的变量),则应用相同的规则,之外,闭包引用计为另一个引用到实例,因此间接地对类和包含的属性。

因此,考虑下面的例子,我们在哪里创建一个闭包:

class Dog():
    breed = 'electronic dog'
    collar_type = 'microsoft'

def foo():
    sparky = Dog()
    def bar():
        return sparky.breed
    return bar

bar = foo()
del Dog

在上面的示例中,Dog类保留在内存中,因为bar闭包仍然引用该类的实例:

>>> bar.__closure__
(<cell at 0x1012b2280: Dog object at 0x1012b5110>,)
>>> bar.__closure__[0].cell_contents
<__main__.Dog object at 0x1012b5110>
>>> bar()
'electronic dog'

答案 1 :(得分:1)

显然,在您向我们展示的代码中,根本没有闭包(由于全局变量)。我认为这只是一个片段。看一下这段代码(作为例子):

def test():
  class Dog():
    breed = 'electronic dog'
    collar_type = 'microsoft'

  sparky=Dog()
  def get_dog_info():
    return sparky.breed

  print get_dog_info.func_closure

test()

表示整个对象sparky已在get_dog_info中“关闭”。事实上,这必须是这样的,因为检索对象的属性需要一些关于对象的知识(breed可以是一个属性)。所以没有地方可以改善它。

答案 2 :(得分:1)

作为Martijn's answer的补充,我将添加以下内容,说明为什么Dog对象(sparky)存储在闭包中而不是字符串(sparky.breed ),我认为这至少是你问题的一部分。

这是因为.运算符的工作方式 - 它在函数调用时访问breed的{​​{1}}属性,因此整个sparky必须存储对象。如果只想在闭包中存储一个字符串,则必须更改函数代码以直接引用该字符串。

换句话说,给出以下内容......

sparky

...你可以看到函数的闭包含一个>>> class Dog(): ... breed = 'electronic dog' ... collar_type = 'microsoft' ... >>> def get_dog_info_closure(): ... sparky = Dog() ... def get_dog_info(): ... return sparky.breed ... return get_dog_info >>> get_dog_info = get_dog_info_closure() 对象,而不仅仅是Dog返回的字符串:

sparky.breed

这意味着您可以检索>>> get_dog_info.func_closure (<cell at 0x10049fa28: instance object at 0x1004a1cf8>,) >>> get_dog_info.func_closure[0].cell_contents <__main__.Dog instance at 0x1004a1cf8> 对象并对其进行修改,以后的调用将反映该修改:

Dog

要仅存储>>> get_dog_info.func_closure[0].cell_contents.breed = ('actual ' 'flesh-and-blood dog!') >>> get_dog_info() 'actual flesh-and-blood dog!' 字符串,您必须单独引用它:

breed