在内部函数内部分配外部变量的python闭包
Posted
技术标签:
【中文标题】在内部函数内部分配外部变量的python闭包【英文标题】:python closure with assigning outer variable inside inner function 【发布时间】:2012-08-18 23:44:30 【问题描述】:我有这段代码:
#!/usr/bin/env python
def get_match():
cache=[]
def match(v):
if cache:
return cache
cache=[v]
return cache
return match
m = get_match()
m(1)
如果我运行它,它会说:
UnboundLocalError: local variable 'cache' referenced before assignment
但如果我这样做:
#!/usr/bin/env python
def get():
y = 1
def m(v):
return y + v
return m
a=get()
a(1)
它运行。
列表有什么东西吗?还是我的代码组织错了?
【问题讨论】:
您的第二个示例没有类似于cache=[v]
的行。这使得 Python 将变量视为局部变量,使函数内的早期引用非法。
你的条件不应该是if v in cache:
吗?否则,这是一个“缓存”,仅包含第一次调用的参数
我忍不住把你的代码和记忆联系起来,你打算这样做吗?在这种情况下,请查看***.com/a/3377272/321973
这能回答你的问题吗? UnboundLocalError with nested function scopes
【参考方案1】:
问题是变量cache
不在函数匹配的范围内。如果您只想像在第二个示例中那样读取它,这不是问题,但是如果您正在分配它,python 会将其解释为局部变量。如果您使用的是 python 3,则可以使用 nonlocal
关键字来解决此问题 - 不幸的是,对于 python 2,没有简单的解决方法。
def f():
v = 0
def x():
return v #works because v is read from the outer scope
def y():
if v == 0: #fails because the variable v is assigned to below
v = 1
#for python3:
def z():
nonlocal v #tell python to search for v in the surrounding scope(s)
if v == 0:
v = 1 #works because you declared the variable as nonlocal
问题与全局变量有些相同 - 每次分配给全局变量时都需要使用global
,而不是读取它。
简要解释其背后的原因:
python 解释器将所有函数编译成function
类型的特殊对象。在此编译期间,它检查函数创建的所有局部变量(用于垃圾收集等)。这些变量名保存在函数对象中。由于“隐藏”外部范围变量(创建具有相同名称的变量)是完全合法的,因此假定分配给且未显式声明为 global
(或 python3 中的 nonlocal
)的任何变量是一个局部变量。
当函数执行时,解释器必须查找它遇到的每个变量引用。如果在编译过程中发现该变量是本地变量,则在函数 f_locals 字典中进行搜索。如果尚未分配,则会引发您遇到的异常。如果变量未在函数范围内分配,因此不是其局部变量的一部分,则在周围范围内查找它 - 如果在周围找不到它,则会引发类似的异常。
【讨论】:
没问题 :) 我已经扩展了一点来描述解释器中发生的事情。 @Brandon 这是一个简单但有效的性能优化 - 局部变量将作为指向 PyObject 的指针存储在 PyFrameObject 的数组中;获取或设置局部变量就像索引数组一样简单。外部作用域的变量必须通过更复杂的过程来查找;有关详细信息,请参阅the source(本地人为 LOAD_FAST,否则为 LOAD_NAME/GLOBAL/DEREF)。而且我认为当你想要覆盖一个全局变量时必须声明它是很好的:就像 Zen 状态一样,explicit > implicit
。
哇,很好的解释。
看来python可以在未定义变量时自动查找非本地范围。【参考方案2】:
访问变量与分配变量不同。
全局变量也有类似的情况。您可以在任何函数中访问它们,但如果您尝试在没有 global
语句的情况下对其进行分配,它将在本地上下文中重新声明它。
不幸的是,对于本地函数,没有等效的 global
语句,但您可以通过替换来绕过重新声明
cache=[v]
与:
cache[:] = [v]
【讨论】:
在 Python 3 中有nonlocal
关键字。
我没有得到你的替换 - cache[:] 创建列表的副本(这显然有效,因为你现在不再分配给缓存)并分配给这个副本 - 但是副本立即被丢弃,因此您实际上并未分配给变量。因此,虽然您的替换修复了错误消息,但它实际上并没有做任何有用的事情......
@l4mpi:不正确。 cache[:]
在赋值的右边时创建一个副本,但是当它在左边时,它表示赋值给一个切片,这是一个变异手术。要查看实际情况,请将其粘贴到解释器中:a=[]; a_id = id(a); a[:] = [1,2,3]; id(a) == a_id
。它将返回True
。
@StevenRumbalski,感谢您提供的信息,我不知道切片操作的这种用法 - 赞成答案。好的,因为它在列表上调用__setslice__
,所以这行得通,但仍然有一些限制:显然它只适用于列表而不是任意变量类型;如果变量已用None
而不是[]
初始化,它将失败;右侧只能包含可迭代对象,这意味着您不能将变量设置为 None 或将其引用更改为另一种类型甚至另一个列表(如 a[:]=b
其中 b 是可迭代的仅复制 b,因此修改 a 不会影响 b )。【参考方案3】:
由于 Python 看到 cache=[v]
- 分配给 cache
,它会将其视为局部变量。所以这个错误是相当合理的——在if
语句中使用之前没有定义局部变量cache
。
你可能想写成:
def get_match():
cache=[]
def match(v):
if cache:
return cache
cache.append(v)
return cache
return match
m = get_match()
m(1)
强烈推荐阅读:Execution Model - Naming and binding 和 PEP 227 - Statically Nested Scopes
【讨论】:
【参考方案4】:替换
cache=[]
def match(v):
与
def match(v,cache=[])
解释:您的代码将cache
声明为get_match
的变量,返回的match(v)
对此一无所知(由于以下赋值)。但是,您确实希望 cache
成为 match
命名空间的一部分。
我知道“恶意”用户可以通过这种方式重新定义缓存,但这是他们自己的错误。 如果这是一个问题,替代方案是:
def match(v):
try:
if cache:
return cache
except NameError:
cache = []
...
(见here)
【讨论】:
捕获NameError
对 OP 不起作用,因为它总是会被提升。见this contrived example。
@StevenRumbalski 谢谢,我还不明白为什么,但我会尝试......无论如何,the other way 有效
here 是包含缓存的代码,我理解缓存应该工作的方式...(if v in cache
和 cache.append(v)
而不是 if cache
和 cache = [v]
)以上是关于在内部函数内部分配外部变量的python闭包的主要内容,如果未能解决你的问题,请参考以下文章