一个 obj.__closure__ 中究竟包含了什么?

53

Beazley在第100页提到:

>>>python.__closure__
(<cell at 0x67f50: str object at 0x69230>,)
>>>python.__closure__[0].cell_contents

我理解的是__closure__是一个列表,但这些cell和str对象是什么?它看起来像是一个一元元组?


最近我写了一篇回答,解释了为什么我们会遇到“NameError: free variable 'var' referenced before assignment”错误。如果您想看看,我也解答了您的问题:https://dev59.com/PIHba4cB1Zd3GeqPRXRt#74336043 - S.B
4个回答

79

闭包单元是指函数所需的值,但这些值来自其周围的范围。

当Python编译嵌套函数时,它记录在代码对象中嵌套函数和父作用域的co_freevarsco_cellvars属性,在父函数中引用但仅在父函数(而不是全局)中定义的任何变量。随后,当您实际创建嵌套函数(在执行父函数时发生),这些引用便被用于将一个闭包附加到嵌套函数上。

函数闭包持有一个元组,每个自由变量都有一个单元格(在co_freevars中命名);单元格是对父作用域的本地变量的特殊引用,跟随这些本地变量所指向的值。以下是最好的示例说明:

def foo():
    def bar():
        print(spam)

    spam = 'ham'
    bar()
    spam = 'eggs'
    bar()
    return bar

b = foo()
b()
在上述示例中,函数bar有一个闭包单元,它指向函数foo中的spam。该单元跟随spam的值。更重要的是,一旦foo()完成并返回bar,该单元将继续引用该值(即字符串eggs),即使在foo内部变量spam不再存在。

因此,上述代码输出:

>>> b=foo()
ham
eggs
>>> b()
eggs

并且 b.__closure__[0].cell_contents'eggs'

请注意,在调用 bar() 时解除引用闭包;闭包在这里不捕获值。当您生成引用循环变量的嵌套函数(使用lambda表达式或def语句)时,这会产生差异:

def foo():
    bar = []
    for spam in ('ham', 'eggs', 'salad'):
        bar.append(lambda: spam)
    return bar

for bar in foo():
    print bar()

上面的代码会连续打印salad三次,因为所有三个lambda函数引用了spam变量,而不是当函数对象创建时它所绑定的值。在for循环结束时,spam被绑定到了'salad',因此所有三个闭包都将解析为该值。


12
提醒一下,__closure__ 中元素的顺序是任意的(按哈希表排序)。如果你想将自由变量的名称与其值匹配,请结合使用 __code__.co_freevars__closure__[<index>].cell_contents。第一个的 co_freevars 的第 i 个对应于 __closure__ 的第 i 个索引。 - Illya Gerasymchuk
6
你可以使用inspect.getclosurevars()函数,该函数会为你合并名称。具体请参考:https://docs.python.org/3/library/inspect.html#inspect.getclosurevars - Martijn Pieters
@IllyaGerasymchuk 您的评论说第一个的索引i(读作:i)对应于第二个的索引i(读作:i prime)。这个prime是打错了吗?它应该是code.co_freevars [i]对应于__closure__ [i]吗?此外,以下链接提到了closurecell_contentcell`:https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy - joseville
1
@joseville:前引号是一个打字错误,应该使用反引号围绕 i 来标记该字符串为代码。作为管理员,我有权更正评论,所以我已经更正了。 - Martijn Pieters

13
这是旧版func_closure的Python 3新名称。 http://docs.python.org/3.0/whatsnew/3.0.html 命名为__X__形式的函数属性已重命名,以便在函数属性命名空间中为用户定义的属性释放这些名称。即,func_closurefunc_codefunc_defaultsfunc_dictfunc_docfunc_globalsfunc_name分别重命名为__closure____code____defaults____dict____doc____globals____name__
简而言之: __closure__None或包含函数自由变量绑定的单元格tuple
并且它不可写。
参考:https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy 示例 Python < 3 (因此我使用func_closure)
def foo():
    x = "I am used"
    y = "I am free"
    z = "I am free too"

    def bar(x):
        return x, y, z

    return bar

c = foo().func_closure

print [i.cell_contents for i in c]

输出:

>>> 
['I am free', 'I am free too']

foo 返回函数 bar,它使用自己的值 x,而不是 yz。因此,它们属于 __closure__


type.htlm的链接似乎已经失效了。关于__closure__和cell contents的信息可以在这里找到:https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy - joseville

4
当在Python中定义一个嵌套函数(闭包)时: 外部函数使用 co_cellvars 来记录在外部函数中定义的变量,这些变量可能会被内部函数引用。 内部函数使用 co_freevars 来记录在外部函数中定义的变量,这些变量可能会在以后被引用。
示例:
# python3
Python 3.4.5 (default, May 29 2017, 15:17:55) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def foo(n):
...     a = 1
...     def g(n):
...             return a - n
...     return g
... 
>>> foo.__closure__
>>> foo.__code__.co_freevars
()
>>> foo.__code__.co_cellvars
('a',)
>>> foo(0).__closure__
(<cell at 0x7f2cd98db1c8: int object at 0x7f2cd9847960>,)
>>> foo(0).__closure__[0].cell_contents
1
>>> foo(0).__code__.co_freevars
('a',)
>>> foo(0).__code__.co_cellvars
()

2
>>> def f():
...     a = "HELO"
...     b = 1.0
...     def w(c):
...         return a,b,c
...     return w

>>> w = f()
>>> w.__closure__
(<cell at 0xa05c4ac: str object at 0x9e91b74>, <cell at 0xa05c3bc: float object at 0xb733dde8>)
>>> w.__closure__[0].cell_contents
'HELO'
>>> w.__closure__[1].cell_contents
1.0

我从未在其他地方看到过使用 cell 类型。它似乎是专门用来保存闭包变量的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接