鱼C论坛

 找回密码
 立即注册
查看: 3304|回复: 5

[已解决]为什么内部函数可以修改外部函数的列表而不能修改变量

[复制链接]
发表于 2023-1-16 11:14:02 | 显示全部楼层 |阅读模式
25鱼币
本帖最后由 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()
可以正常运行,无报错

为什么列表可以修改而变量不可以呢
最佳答案
2023-1-16 11:14:03
本帖最后由 阿奇_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    # 自由变量有吗?有哪些?

最佳答案

查看完整内容

自己吃饱后,滚回去翻书复习了。。这里涉及几个技术术语和概念: 作用域、自由变量、闭包、可变数据类型和不可变数据类型 我尝试着解释吧: 作用域,我就不多解释了,分:局部(本地)作用域、全局作用域。(基本就看变量被定义相对位置) 自由变量free variable:指 没有在本地作用域里进行绑定的非全局变量,如你这里b函数作用域里的cnt 闭包closure:是一个"函数"(或叫一种机制),它可以保存自由变量的绑定 ...
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-1-16 11:14:03 | 显示全部楼层    本楼为最佳答案   
本帖最后由 阿奇_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    # 自由变量有吗?有哪些?

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-1-16 11:16:01 | 显示全部楼层
Python 的变量分可变和不可变

可变:

list
set
dict
不可变:

int
float
tuple
str
对于不可变变量,作为参数的时候,会被复制一份,可以看做是值传递

对于可变变量,作为参数的时候,可以看做是引用传递
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-1-16 11:20:57 From FishC Mobile | 显示全部楼层
本帖最后由 阿奇_o 于 2023-1-16 11:32 编辑

你试试把 cnt += 1 改为 cnt = cnt + 1
^_^  
----- 啊也不行?
这就尴尬了。。 待我研究研究。。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-1-17 08:44:24 | 显示全部楼层
本帖最后由 Mta123456 于 2023-1-17 08:46 编辑

别忘了变量是全局变量,内部函数只是局部变量,你要么用nonlocal声明,否则只能在内部函数中复制一个一模一样的变量。但列表和变量是有区别的。你在内部函数使用append()方法,并没有改变原来列表的id值,所以列表还是原来的列表,只不过在内部函数中多了一个名称一样但id值不同的列表
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-1-17 09:29:36 | 显示全部楼层
本帖最后由 月光沙漠 于 2023-1-17 09:41 编辑

cnt = 0      cnt 指向0的内存地址
cnt+=1      0是字符串,是不可变类型,cnt+=1后,只能在内存中新建一个字符串1,将cnt指向1的内存地址
cnt = []     cnt 指向空列表[]的内存地址,列表是可变类型,append后,列表中的元素变化了,但还是同一个内存地址

你可以在函数中print(id(cnt))来比较一下。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-11-16 05:39

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表