简体   繁体   English

python中的闭包是否捕获对象引用,或者仅捕获它们需要的属性?

[英]Do closures in python capture object references, or just the attributes they need?

This question is about how intelligently python does its escape analysis. 这个问题是关于python如何进行逃逸分析。

Lets say I have the following program: 可以说我有以下程序:

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

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

The function get_dog_info() clearly will have to close over sparky.breed. 函数get_dog_info()显然必须在sparky.breed上关闭。 But, to do so, does the implementation also escape the entire Dog object? 但是,这样做是否也可以实现整个Dog对象的转义? That is, would there be an additional memory cost to close over collar_type as well? 就是说,是否也需要花费额外的内存来关闭衣领? Or is this a choice that is left up to the implementation? 还是这取决于实施?

Both Dog and sparky are referred to by the global namespace that forms your module, keeping both in memory. Dogsparky都由构成您的模块的全局命名空间引用,并同时保留在内存中。

If you were to run del Dog , sparky would still refer to the class (through it's __class__ reference) keeping it alive. 如果要运行del Dog ,则sparky仍将引用该类(通过__class__引用),以使其保持活动状态。 The class refers to the two attributes that are part of it's definition, so they are kept alive as well. 该类引用了属于其定义的两个属性,因此它们也保持活动状态。 This is all independent of the get_dog_info function. 这些都独立get_dog_info函数。

CPython keeps objects in memory based on reference counts; CPython根据引用计数将对象保留在内存中; if anything in Python starts refering to an object somethere , that object's reference count is increased by 1, and decreased again when the reference is removed. 如果Python中的任何内容开始引用对象somethere ,则该对象的引用计数将增加1,并在删除引用后再次减少。 When the count drops to 0, the object is removed from memory, and a garbage collection process breaks up circular references as needed to facilitate this process. 当计数下降到0时,将从内存中删除该对象,并且垃圾回收过程会根据需要中断循环引用以促进此过程。

Note that because sparky is a global, the function code does not directly reference anything; 请注意,由于sparky是全局变量,因此功能代码不会直接引用任何内容。 globals are looked up at runtime. 全局变量在运行时查找。 If you were to delete sparky too, all references would be cleared up. 如果您也要删除sparky ,所有引用将被清除。 Because sparky in get_dog_info() is looked up in the global namespace, calling get_dog_info() would then result in a NameError . 由于sparkyget_dog_info()是在全局命名空间抬头,呼吁get_dog_info()然后将导致NameError

If you did have a closure (reference to a variable in a parent function scope), the same rules would apply, except that the closure reference counts as another reference to the instance, thus indirectly to the class and the contained attributes. 如果确实有闭包(在父函数作用域中对变量的引用),则将应用相同的规则, 不同之处在于闭包引用将作为对实例的另一个引用,从而间接对类和所包含的属性进行引用。

So, considering the following example, where we do create a closure: 因此,考虑以下示例,我们将在其中创建一个闭合:

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

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

bar = foo()
del Dog

In the above example the Dog class remains in memory, because the bar closure still refers to an instance of that class: 在上面的示例中, Dog类保留在内存中,因为bar闭包仍然引用该类的实例:

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

Obviously in the code you've shown us there is no closure at all ( due to globals ). 显然,在代码中,您向我们展示了根本没有闭包(由于globals)。 I assume it's just a snippet. 我认为这只是一个片段。 Have a look at this code ( as an example ): 看下面的代码(作为示例):

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()

which shows that entire object sparky has been "closed" in get_dog_info . 这表明整个对象sparky已在“闭合” get_dog_info And indeed that has to be like that, because retrieving attribute of an object requires some knowledge about the object ( breed can be a property for example ). 确实必须这样,因为检索对象的属性需要有关该对象的一些知识(例如, breed可以是属性)。 So there is no place to improve that. 因此,没有地方可以改善它。

As a supplement to Martijn's answer , I'll add the following about why a Dog object ( sparky ) is stored in the closure rather than a string ( sparky.breed ), which I think is at least part of your question. 作为Martijn答案的补充,我将添加以下内容,说明为什么将Dog对象( sparky )存储在闭包中而不是字符串( sparky.breed )中,我认为这至少是您的问题的一部分。

This is because of the way the . 这是因为的方式. operator works -- it accesses the breed attribute of sparky at the time of function invocation, and so the whole sparky object has to be stored. 运算符起作用-在函数调用时访问sparkybreed属性,因此必须存储整个sparky对象。 If you wanted to store only a string in the closure, you'd have to change the function code to refer directly to the string. 如果您只想在闭包中存储字符串,则必须更改功能代码以直接引用该字符串。

So in other words, given the following... 因此,换句话说,给出以下...

>>> 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()

...you can see that the function's closure contains a Dog object, rather than simply the string returned by sparky.breed : ...您可以看到该函数的闭包包含一个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>

This means that you can retrieve the Dog object and modify it, and future invocations will reflect that modification: 这意味着您可以检索Dog对象并对其进行修改,以后的调用将反映该修改:

>>> get_dog_info.func_closure[0].cell_contents.breed = ('actual '
                                                        'flesh-and-blood dog!')
>>> get_dog_info()
'actual flesh-and-blood dog!'

To store only the breed string, you'd have to refer to it separately: 要仅存储breed字符串,您必须单独引用它:

>>> def get_dog_info_closure():
...     sparky = Dog()
...     sbreed = sparky.breed
...     def get_dog_info():
...         return sbreed
...     return get_dog_info
... 
>>> get_dog_info = get_dog_info_closure()
>>> get_dog_info.func_closure[0].cell_contents
'electronic dog'

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM