为什么内部函数可以修改外部函数的列表而不能修改变量
本帖最后由 windcut 于 2023-1-16 11:16 编辑rt,如果代码这样写
def a():
cnt = 0
def b():
cnt += 1 #修改外部函数的变量
return b
c = a()
c()
会报错:UnboundLocalError: local variable 'cnt' referenced before assignment
必须使用nonlocal语句才可以修改外部函数的变量
但如果代码这样写
def a():
cnt = []
def b():
cnt.append("qwq") #修改外部函数的列表
return b
c = a()
c()
可以正常运行,无报错
为什么列表可以修改而变量不可以呢 本帖最后由 阿奇_o 于 2023-1-16 15:16 编辑
阿奇_o 发表于 2023-1-16 11:20
你试试把 cnt += 1 改为 cnt = cnt + 1
^_^
----- 啊也不行?
自己吃饱后,滚回去翻书复习了。。这里涉及几个技术术语和概念:
作用域、自由变量、闭包、可变数据类型和不可变数据类型
我尝试着解释吧:
作用域,我就不多解释了,分:局部(本地)作用域、全局作用域。(基本就看变量被定义相对位置)
自由变量free variable:指 没有在本地作用域里进行绑定的非全局变量,如你这里b函数作用域里的cnt
闭包closure:是一个"函数"(或叫一种机制),它可以保存自由变量的绑定。
为什么要这样?—— 因为调用时,因为a函数的return b的缘故,a函数的作用域已经不存在了!
所以,为了能继续使用原定义的变量绑定,特别地,采用了【闭包】这一函数/机制来保存这些"自由变量"。
但问题是。。如果你在"内部函数"里修改、更新(重新绑定)这些变量 的时候,它就会创建的是"本地变量"(local variable),而非"自由变量"
—— 本地变量,就要遵守本地变量的"先声明后使用"的规则(其实任何变量实质上都要),于是你就看到了那样的报错。
那 cnt.append('qwq') 的情况,为什么没报错?—— 因为这不是更新或修改cnt,而是更新了其内部的元素。
cnt = [] 是列表,是可变类型,append()并没有改变cnt(它还是它,id没变),所以 cnt 依然是自由变量。
而 对于不可变类型的 cnt = 0 , 后cnt += 1 或 cnt = cnt + 1 这样的情况,就重新绑定cnt了,这会 隐式地创建局部变量,
于是,cnt并没有成为"自由变量",故 执行时,它会报错 UnboundLocalError: local variable 'cnt' referenced before assignment。
其实,你在 cnt.append('qwq') 后,加多一句 cnt = cnt[:] ,执行时,基于上述原因,它同样会报错。
def f2():
cnt = []
def b():
cnt.append(10)
cnt = cnt[:]
return b
t = f2()
t()
Traceback (most recent call last):
File "<pyshell#27>", line 1, in <module>
t()
File "<pyshell#25>", line 4, in b
cnt.append(10)
UnboundLocalError: local variable 'cnt' referenced before assignment
如果你能理解这个,应该可以"有点明白了"。。 哈哈
ps: 我想我没完全解释清楚。。主要是太细了。。另外也需要先搞明白有些前置知识和概念。。
ps2: 偷偷告诉你,你可以通过 函数对象的 __code__属性来查看其包含的 局部变量和自由变量, 如
t.__code__.co_varnames# 局部(本地)变量有哪些?
t.__code__.co_freevars # 自由变量有吗?有哪些?
Python 的变量分可变和不可变
可变:
list
set
dict
不可变:
int
float
tuple
str
对于不可变变量,作为参数的时候,会被复制一份,可以看做是值传递
对于可变变量,作为参数的时候,可以看做是引用传递 本帖最后由 阿奇_o 于 2023-1-16 11:32 编辑
你试试把 cnt += 1 改为 cnt = cnt + 1
^_^
----- 啊也不行?
这就尴尬了。。 待我研究研究。。 本帖最后由 Mta123456 于 2023-1-17 08:46 编辑
别忘了变量是全局变量,内部函数只是局部变量,你要么用nonlocal声明,否则只能在内部函数中复制一个一模一样的变量。但列表和变量是有区别的。你在内部函数使用append()方法,并没有改变原来列表的id值,所以列表还是原来的列表,只不过在内部函数中多了一个名称一样但id值不同的列表 本帖最后由 月光沙漠 于 2023-1-17 09:41 编辑
cnt = 0 cnt 指向0的内存地址
cnt+=1 0是字符串,是不可变类型,cnt+=1后,只能在内存中新建一个字符串1,将cnt指向1的内存地址
cnt = [] cnt 指向空列表[]的内存地址,列表是可变类型,append后,列表中的元素变化了,但还是同一个内存地址
你可以在函数中print(id(cnt))来比较一下。
页:
[1]