为什么会出现这个UnboundLocalError错误(闭包)?

218

我在这里做错了什么?

counter = 0

def increment():
  counter += 1

increment()

上面的代码会抛出一个UnboundLocalError异常。


1
这个问题以及它当前被标记为重复的问题正在Python聊天室中讨论。 - Zero Piraeus
5
这里许多答案都建议使用 global,虽然这种方法可以解决问题,但通常不建议使用可修改的全局变量,除非没有其他更好的选择。 - PM 2Ring
20
2012年提出的问题不可能是2016年提出的问题的重复,相反,新的问题才是重复的。 - dsh
10
“那不是真的。” - Zero Piraeus
显示剩余4条评论
8个回答

220

Python没有变量声明,因此必须自行确定变量的作用域。它通过一个简单的规则来实现:如果在函数内部对变量进行赋值,则该变量被视为局部变量。[1] 因此,下面这行代码:

counter += 1

隐式地将counter作为increment()的本地变量。但是,尝试执行此行会在分配之前尝试读取本地变量counter的值,导致UnboundLocalError[2]

如果counter是一个全局变量,则global关键字会有所帮助。如果increment()是一个本地函数,而counter是一个本地变量,则可以在Python 3.x中使用nonlocal


1
一个注意点让我有些困惑,我在文件顶部声明了一个变量,在函数内部可以轻松读取,但是如果要写入这个在文件顶部声明的变量,我必须使用global。 - mouckatron
更深入的解释请参考:https://docs.python.org/3.3/reference/executionmodel.html#naming-and-binding。不仅赋值可以绑定名称,导入也可以,因此您可能会从使用未绑定导入名称的语句中获得`UnboundLocalError`。例如:`def foo(): bar = deepcopy({'a':1}); from copy import deepcopy; return bar,然后 from copy import deepcopy; foo()。如果删除本地导入from copy import deepcopy`,则调用将成功。 - Yibo Yang

95

您需要使用global语句,以便修改全局变量计数器,而不是本地变量:

counter = 0

def increment():
  global counter
  counter += 1

increment()

如果定义 counter 的封闭作用域不是全局作用域,那么在Python 3.x中可以使用nonlocal语句。 在Python 2.x中,在同样的情况下,您将无法重新分配非本地名称counter,因此您需要使counter可变并对其进行修改:

counter = [0]

def increment():
  counter[0] += 1

increment()
print counter[0]  # prints '1'

30

回答您标题中的问题,是的,在Python中有闭包,但它们只适用于函数内部,并且(在Python 2.x中)是只读的;您不能将名称重新绑定到不同的对象上(尽管如果对象是可变的,您可以修改其内容)。 在Python 3.x中,您可以使用nonlocal关键字来修改闭包变量。

def incrementer():
    counter = 0
    def increment():
        nonlocal counter
        counter += 1
        return counter
    return increment

increment = incrementer()

increment()   # 1
increment()   # 2

* 该问题最初是关于Python中闭包的。


7
你的代码抛出 UnboundLocalError 的原因已经在其他答案中得到了很好的解释。
但我觉得你正在尝试构建一个类似于 itertools.count() 的东西。
所以试一下,看看它是否适合你的情况:
>>> from itertools import count
>>> counter = count(0)
>>> counter
count(0)
>>> next(counter)
0
>>> counter
count(1)
>>> next(counter)
1
>>> counter
count(2)

5

Python具有默认的词法作用域,这意味着尽管封闭作用域可以访问其封闭作用域中的值,但它不能修改它们(除非使用global关键字声明为全局变量)。

闭包将封闭环境中的值绑定到本地环境中的名称。然后,本地环境可以使用绑定的值,甚至重新分配该名称以指向其他内容,但它无法修改封闭环境中的绑定。

在您的情况下,您试图将counter视为本地变量而不是绑定值。请注意,此代码绑定了在封闭环境中分配的x的值,可以正常工作:

>>> x = 1

>>> def f():
>>>  return x

>>> f()
1

3
为了在函数内部修改全局变量,必须使用global关键字。
如果您没有使用这行代码就尝试修改,则会出现错误。
global counter

在增量的定义内,创建了一个名为counter的本地变量,以防止您破坏整个程序可能依赖的计数器变量。
请注意,只有在修改变量时才需要使用global。您可以在不需要global语句的情况下从增量中读取计数器。

1

试试这个:

counter = 0

def increment():
  global counter
  counter += 1

increment()

-2

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