为什么Python的+=运算符不修改内部函数的变量?

5 投票
4 回答
2109 浏览
提问于 2025-04-16 22:07

我想了解一下,为什么这个没有按照预期工作:

def outer():
    mylist = []
    def inner():
        mylist += [1]

    inner()

outer()

特别是因为 mylist.__iadd__([1]) 这个是正常的。

4 个回答

1

这是因为在 inner() 函数里面的 myList 并不是指向在 outer() 函数里定义的 myList,所以加法赋值操作没有效果。我想到两个解决办法。

第一个办法是把 myList 作为参数传递给 inner 函数:

def outer():
    mylist = []
    def inner(someList):
        somelist += [1]

    inner(mylist)

outer()

第二个办法是在两个函数外面先定义 myList,然后在两个函数里面用 global 来声明它:

mylist = []
def outer():
    global mylist
    mylist = []
    def inner():
        global mylist
        mylist += [1]

    inner()

outer()

不过我更推荐第一个办法。

6

如果在一个函数里给一个名字(变量)赋了值,那么这个名字就被当作局部变量,除非你特别声明它是全局的。

所以在inner这个函数里,mylist就是一个局部变量。

当你写x += y的时候,Python在运行时会先尝试:

x = x.__iadd__(y)

如果这个尝试失败了,Python会接着尝试:

x = x.__add__(y)
9

问题在于,当你在一个函数内部给一个变量命名时,Python会认为你想创建一个新的局部变量,这个变量会遮盖外部作用域中同名的变量。因为 += 需要先获取 mylist 的值才能进行修改,所以它会报错,因为局部的 mylist 还没有被定义。MRAB的回答 对这个问题的解释很清楚。

另一方面,当你使用 mylist.__iadd__([1]) 时,你并没有在函数内部创建一个新的变量名。你只是用一个内置的方法来修改一个已经存在的变量名。只要你不尝试给 mylist 赋一个新值,就不会有问题。出于同样的原因,如果在 outer 中定义的 mylistmylist = [1],那么在 inner 中, mylist[0] = 5 也能正常工作。

不过要注意,如果你在函数的任何地方尝试给 mylist 赋一个新值, mylist.__iadd__([1]) 确实会失败:

>>> outer()
>>> def outer():
...     mylist = []
...     def inner():
...         mylist.__iadd__([1])
...         mylist = []
...     inner()
... 
>>> outer()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in outer
  File "<stdin>", line 4, in inner
UnboundLocalError: local variable 'mylist' referenced before assignment

如果你想从外部作用域给一个变量赋新值,在 Python 3.0 及以上版本中,你可以使用 nonlocal,用法和 global 类似,都是用来给变量赋新值的。所以,不是这样:

>>> mylist = []
>>> def inner():
...     global mylist
...     mylist += [1]
... 
>>> inner()
>>> mylist
[1]

而是这样:

def outer():
    mylist = []
    def inner():
        nonlocal mylist
        mylist += [1]
    inner()
    print(mylist)
outer()

撰写回答