嵌套函数作用域中的UnboundLocalError

79 投票
3 回答
19983 浏览
提问于 2025-04-15 21:25

我有这样的代码(简化版):

def outer():
    ctr = 0

    def inner():
        ctr += 1

    inner()

但是 ctr 出现了错误:

Traceback (most recent call last):
  File "foo.py", line 9, in <module>
    outer()
  File "foo.py", line 7, in outer
    inner()
  File "foo.py", line 5, in inner
    ctr += 1
UnboundLocalError: local variable 'ctr' referenced before assignment

我该怎么解决这个问题呢?我以为嵌套作用域可以让我这样做。我试过使用 'global',但还是不行。

3 个回答

-2

如果把 ctr 这个变量放在 outer 之外(也就是放在全局范围内),或者放在其他的类/函数里,会怎么样呢?这样做的话,这个变量就可以被访问和修改了。

53

解释

每当在一个函数内部给一个变量赋值时,Python就会把这个变量当作该函数的一个局部变量。这意味着即使赋值的操作没有执行,只要在函数里有赋值的代码,Python就会认为这个变量是局部的。比如说,ctr += 1这行代码包含了对ctr的赋值,所以Python认为ctrinner函数的局部变量。因此,它根本不会去查看在outer函数中定义的ctr的值。Python看到的情况大致是这样的:

def inner():
    ctr = ctr + 1

大家应该都同意,这段代码会出错,因为ctr在被定义之前就被访问了。

(想了解更多关于Python如何决定变量作用域的内容,可以查看官方文档这个问题。)

解决方案(在Python 3中)

Python 3引入了nonlocal语句,它的作用和global语句类似,但可以让我们访问外层函数的变量(而不是全局变量)。只需在inner函数的顶部添加nonlocal ctr,问题就解决了:

def outer():
    ctr = 0

    def inner():
        nonlocal ctr
        ctr += 1

    inner()

变通方法(在Python 2中)

由于Python 2中没有nonlocal语句,我们需要想办法解决。这里有两种简单的变通方法:

  • 去掉对ctr的所有赋值

    因为Python只把ctr当作局部变量是因为有赋值操作,所以如果我们去掉对ctr的所有赋值,问题就会消失。但我们怎么能在不赋值的情况下改变这个变量的值呢?很简单:我们把这个变量放在一个可变对象里,比如列表。这样我们就可以修改这个列表,而不需要给ctr赋值:

    def outer():
        ctr = [0]
    
        def inner():
            ctr[0] += 1
    
        inner()
    
  • ctr作为参数传递给inner

    def outer():
        ctr = 0
    
        def inner(ctr):
            ctr += 1
            return ctr
    
        ctr = inner(ctr)
    
102

如果你在使用Python 3,你可以用nonlocal这个关键词来重新绑定一个非本地的名字,也就是说可以改变它的值。

def outer():
    ctr = 0

    def inner():
        nonlocal ctr
        ctr += 1

    inner()

如果你在使用Python 2,因为没有nonlocal这个关键词,你需要在不重新绑定名字的情况下进行加法操作(也就是把计数器作为某个名字的一个项目或者属性来使用,而不是直接作为一个名字)。比如:

...
ctr = [0]

def inner():
    ctr[0] += 1
...

当然,在其他地方使用ctr的时候,要用ctr[0]来代替直接使用ctr

撰写回答