最近在刷题的时候碰到个奇怪的问题。例子大概是这样似的:
In [1]: a = 100
In [2]: def func():
...: print(a)
...:
In [3]: func()
100
In [4]: def another_func():
...: a += 100
...: print(a)
...:
In [5]: another_func()
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
<ipython-input-5-464bd62798b3> in <module>()
----> 1 another_func()
<ipython-input-4-1674a1f57571> in another_func()
1 def another_func():
----> 2 a += 100
3 print(a)
4
UnboundLocalError: local variable 'a' referenced before assignment
为什么直接用print可以输出a,但是调用a的话就报错说变量a未分配?这是Python语言的特性,不同于C语言的是**Python函数中的变量(在定义前直接调用时)会优先引用局部变量,查找不到时再去引用全局变量。**但是第二个函数怎么解释呢?这里是因为Python函数中的变量第一次出现在等号右边时会被当做一个新的局部变量(提前用global声明过的全局变量除外)。在函数中直接运算a += 100并不会像直接打印a一样调用全局变量,而调用的是还没来得及赋值的局部变量a,所以才会报错。
In [1]: def func(num):
...: num += 1
...: print(id(num))
...:
In [2]: num = 0
In [3]: id(num)
Out[3]: 1953497520
In [4]: func(num)
1953497552
In [5]: lists = [1,2,3]
In [6]: def list_handle(lists):
...: lists.append(4)
...:
In [7]: list_handle(lists)
In [8]: lists
Out[8]: [1, 2, 3, 4]
In [9]: string = 'hello'
In [10]: print(id(string))
1861356044448
In [11]: def string_handle(string):
...: string = ' '.join([string, 'world'])
...: print(id(string))
...:
In [12]: string_handle(string)
1861357160816
如上述代码所示,如果用形参的方式向函数传值时原理与C语言类似,是将实参的值复制给函数中的局部变量(本质上是增加新局部变量来引用实参的值)。当实参为可变对象时可原地修改传入对象的内容,实参为不可变对象时修改的只是局部变量的值而对传入的实参内容没有影响。
In [13]: def list_append(li=[], num=None):
...: li.append(num)
...: print(li)
...:
In [14]: list_append(num=1)
[1]
In [15]: list_append(num=2)
[1, 2]
另外还有一点需要注意的是,如果定义函数时设定形参默认值为可变对象,那么会出现上面这种情况。当多次使用默认值调用函数时并没有每次都创建新list并append值进去,而一直操作的是之前的那个list。这是因为Python的函数在初次编译时,为可变类型的形参分配一个固定地址,以后在使用缺省值时就一直操作这块地址,想要避免这种情况就尽量不要设置形参的默认值为可变对象或每次使用时不要用缺省值,也可以用以下方式:
In [16]: def list_append(li=None, num=None):
...: if not li:
...: li = []
...: li.append(num)
...: return li
...:
In [17]: list_append(num=1)
Out[17]: [1]
In [18]: list_append(num=2)
Out[18]: [2]