在内部函数内部分配外部变量的python闭包

时间:2012-08-23 12:49:05

标签: python closures scope python-2.x

我有这段代码:

#!/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)

它运行。

列表中有什么东西吗?或者我的代码组织错了?

4 个答案:

答案 0 :(得分:25)

问题是变量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字典中搜索它。如果尚未分配,则会引发您遇到的异常。如果变量未在函数范围内分配,因此不是其本地的一部分,则会在周围的范围中查找 - 如果在那里找不到,则会引发类似的异常。

答案 1 :(得分:7)

访问变量与分配变量不同。

您对全局变量有类似的情况。您可以在任何函数中访问它们,但如果您尝试在没有global语句的情况下分配它们,它将在本地上下文中重新声明它。

不幸的是,对于本地函数,没有等效的global语句,但您可以通过替换

来绕过重新声明
cache=[v]

使用:

cache[:] = [v]

答案 2 :(得分:3)

由于Python看到cache=[v] - 赋值给cache,因此它将其视为局部变量。所以错误是非常合理的 - 在cache语句中使用之前没有定义局部变量if

你可能想把它写成:

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 bindingPEP 227 - Statically Nested Scopes

答案 3 :(得分:0)

替换

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