嵌套函数无法修改外部函数的变量,怎么办?

5
def some_func(a): 
    def access_a():
        print(a)
    access_a()

输出a的值。然而,如果我想像这样在嵌套函数中更改a:

def some_func(a): 
    def change_a():
        a += 1
        print(a)
    change_a()

这段代码会引发UnboundLocalError异常。

虽然我知道a是一个非局部变量,但我为什么可以在不声明nonlocal a的情况下访问它呢?


你在函数中尝试过使用 global a 吗? - Fomalhaut
3
这与“global”的情况相同:您可以访问全局变量,但如果没有使用“global var”,尝试修改它将失败。 - ForceBru
3
无论采取何种方法绕过此问题,都不应该像这样联系并引起任意的副作用,这不是良好的编程实践。 - mVChr
...所以你一开始就不应该这样做。 - martineau
3个回答

11

Python作用域规则101:

  1. 函数体中绑定的名称被认为是局部变量,除非显式声明为全局变量(适用于Python 2.x和3.x)或非局部变量(仅适用于Python 3.x)。这在函数体内发生的任何赋值都是成立的。当然,在绑定之前尝试读取局部变量会导致错误。

  2. 如果在函数体中读取但未绑定的名称,将在封闭作用域中查找(如果有外部函数,则为外部函数,否则为全局作用域)。注意:函数参数实际上是局部名称,因此永远不会在封闭作用域中查找。

请注意,a += 1主要是a = a + 1的简写,因此在您的示例中,a是局部变量(在函数体中绑定且未显式声明为全局或非局部变量),但在读取它之前(a = a+1的右侧)尝试读取它。

在Python 3中,您可以通过使用nonlocal语句来解决这个问题:

>>> def outer(a):
...    def change():
...       nonlocal a
...       a += 1
...    print("before : {}".format(a))
...    change()
...    print ("after : {}".format(a))
... 
>>> outer(42)
before : 42
after : 43

Python 2没有nonlocal关键字,因此常规的解决方法是将变量包装在可变容器中(通常是list,但任何可变对象都可以):

>>> def outer(a):
...     _a = [a]
...     def change():
...         _a[0] += 1
...     print("before : {}".format(_a[0]))
...     change()
...     print ("after : {}".format(_a[0]))
... 
>>> outer(42)
before : 42
after : 43

这种说法非常不雅观。

尽管闭包很方便,但它们主要是对象的函数对应物:一种在保留状态的封装的同时在一组函数之间共享状态的方式,因此,如果您发现需要一个nonlocal变量,也许一个适当的类可能是更清晰的解决方案(虽然对于仅在内部使用而不返回内部函数的示例可能不适用)。


1
我为您提供两个解决方案:
#first one:
# try with list, compound data types dict/list
def some_func(a): 
    def change_a():
        a[0] += 1
        print(a[0])
    change_a()
some_func([1])
>>> 2


#second one
#reference pointer 
from ctypes import *
def some_func_ctypes(a):
    def change_a():
      a[0] += 1
      print a.contents, a[0]
    change_a()

i = c_int(1)
pi = pointer(i)
some_func_ctypes(pi)

>>> c_int(2) 2

OP所问的是为什么他能够_读取_一个非局部变量,但不能_重新绑定_它... - bruno desthuilliers
@bruno,老兄,你的解释/回答很棒,谢谢。我需要再解释一遍吗?我想不需要了,但为未来的读者提供其他解决方案会更好… - Ari Gold

0
当你使用 += 运算符时,会将一个新值赋值给a。这将在解释器的视线中将a变为一个本地变量。

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