植物百科网
当前位置: 首页 农业百科

python闭包怎么解(中的闭包和自由变量)

时间:2023-08-05 作者: 小编 阅读量: 1 栏目名: 农业百科

被引用的非全局变量也称为自由变量。这个自由变量保存在外部函数的只读属性__closure__中,会与内层函数产生一个绑定关系,也就是自由变量将不会在内存中轻易消失。这是因为Python中并没有要求先声明一个变量才能使用它,Python解释器认为:在函数体内,只要对一个变量进行赋值操作,那么这个变量就是局部变量。所以说现在cnt已经不是自由变量了。所以,你看它显示的结果:此时,cnt确实也是自由变量的,但是它是不可变对象啊,所以报了TypeError错误。

python闭包怎么解?在函数内部再定义一个函数,并且这个函数用到了外部函数的变量(LEGB),最后返回新建函数的函数名索引,那么将这样的能够访问其定义时所在的作用域的函数以及用到的一些变量称之为 闭包 被引用的非全局变量也称为 自由变量 这个自由变量保存在外部函数的只读属性 __closure__ 中,会与内层函数产生一个绑定关系,也就是自由变量将不会在内存中轻易消失如下例所示:,今天小编就来聊一聊关于python闭包怎么解?接下来我们就一起去研究一下吧!

python闭包怎么解

1.定义

在函数内部再定义一个函数,并且这个函数用到了外部函数的变量(LEGB),最后返回新建函数的函数名索引,那么将这样的能够访问其定义时所在的作用域的函数以及用到的一些变量称之为 闭包 。被引用的非全局变量也称为 自由变量 。这个自由变量保存在外部函数的只读属性 __closure__ 中,会与内层函数产生一个绑定关系,也就是自由变量将不会在内存中轻易消失。如下例所示:

# 计算函数被调用的次数def counter(FIRST=0):-----------------__closure__---------------|cnt = [FIRST]|# 之所以选列表是因为作用域问题,详见后文| ||def add_one():||cnt[0]= 1||return cnt[0]|------------------------------------------return add_one# 每当外部函数被调用时,都将重新定义内部的函数,而变量 cnt 的值也可能不同num5 = counter(5)num10 = counter(10)print(num5())# 6print(num5())# 7print(num10())# 11print(num10())# 12# 如果这个函数仅仅是嵌套函数,那么它的 __closure__ 应该是 Noneprint(num5.__closure__)# (<cell at 0x0163FE30: list object at 0x01514A80>,)print(num5.__closure__[0].cell_contents)# 7print(num10.__closure__[0].cell_contents)# 12# 或者通过 __code__.co_freevars 查看函数中是否有自由变量,如果有自由变量,即为闭包print(num10.__code__.co_freevars)# ('cnt',)

2.nonlocal 关键字

上面代码中的 cnt 变量是一个列表,可变对象,但如果是不可变对象,如:numer、tuple 等呢?

def counter(FIRST=0):cnt = FIRST# numberdef add_one():cnt= 1return cntreturn add_onenum5 = counter(5)print(num5.__closure__)print(num5.__code__.co_freevars)print(num5())----------------------------------------------------------------------------def counter(FIRST=0):cnt = (FIRST,)# tupledef add_one():cnt[0]= 1return cnt[0]return add_onenum5 = counter(5)print(num5.__closure__)print(num5.__code__.co_freevars)print(num5())

以上实例输出结果:

None()Traceback (most recent call last):File "test.py", line, in <module>print(num5())File "test.py", line, in add_onecnt= 1UnboundLocalError: local variable 'cnt' referenced before assignment----------------------------------------------------------------------------(<cell at 0x0180FE10: tuple object at 0x0173A750>,)('cnt',)Traceback (most recent call last):File "test.py", line, in <module>print(num5())File "test.py", line, in add_onecnt[0]= 1TypeError: 'tuple' object does not support item assignment

可以看出,此时 cnt 不再是自由变量,而是变成了局部变量,且提示 UnboundLocalError 未绑定局部错误。为什么不是自由变量了呢?为什么列表就没问题呢?

这是因为Python 中并没有要求先声明一个变量才能使用它,Python 解释器认为:在函数体内,只要对一个变量进行赋值操作,那么这个变量就是局部变量。

Python的模块代码执行之前,并不会经过预编译,模块内的函数体代码在运行前会经过预编译,因此不管变量名的绑定发生在作用域的那个位置,都能被编译器知道。

而 cnt= 1 相当于 cnt = cnt1,对 cnt 进行了赋值操作,所以 Python 解释器认为 cnt 是函数内的局部变量,但是执行的时候,先执行 cnt 1 时发现:

因为先前已经认定 cnt 为局部变量了,现在在局部作用域内找不到 cnt 的值,也不会再到外部作用域找了,就会报错。所以说现在 cnt 已经不是自由变量了。

那么 tuple 类型的 cnt 呢?首先 cnt[0] = cnt[0]1,虽然有赋值,但是其左边也是 cnt[0],cnt 是从外边作用域索引了的。

所以,你看它显示的结果:此时,cnt 确实也是自由变量的,但是它是不可变对象啊,所以报了 TypeError 错误。这下列表为什么行,你应该知道了。

或者你使用 nonolocal 关键字,这个关键字的用法与 global 很像,让你能够给外部作用域(非全局作用域)内的变量赋值。它可以使得一个被赋值的局部变量变为自由变量,并且 nonlocal 声明的变量发生变化时, __closure__ 中存储的值也会发生变化:

def counter(FIRST=0):cnt = FIRST# numberdef add_one():nonlocal cntcnt= 1return cntreturn add_onenum5 = counter(5)print(num5.__closure__)print(num5.__code__.co_freevars)print(num5())

(<cell at 0x01BFFE30: int object at 0x53E064D0>,)('cnt',)6

nonlocal 和 global

def scope_test():spam = "test spam"def do_nonlocal():nonlocal spamspam = "nonlocal spam"def do_global():global spamspam = "global spam"do_nonlocal()print("After nonlocal assignment:", spam)# nonlocal spamdo_global()print("After global assignment:", spam)# nonlocal spamscope_test()print("In global scope:", spam)# global spam

After nonlocal assignment: nonlocal spamAfter global assignment: nonlocal spamIn global scope: global spam

3.注意事项

lambda 自由参数之坑,特别是和列表解析或for循环结合使用时。lambda para_list : expression == > def (para_list): return expression

#---CASE1fs = [lambda j:i*j for i in range(3)]print([f(2) for f in fs])#---CASE2fs = map(lambda i:(lambda j: i*j), range(3))print([f(2) for f in fs])#---CASE3fs = [(lambda i:lambda j:i*j)(i) for i in range(3)]print([f(2) for f in fs])

[4, 4, 4][0, 2, 4][0, 2, 4]

首先,CASE1 和 CASE3 显然都是每循环一次,就添加一个 lambda 函数到列表中,不同的是,CASE1 添加的 lambda 函数中的 i 每次并没有接收 for 循环中 i 的值,它只是定义的时候指了下 i,所以说,CASE1 中的几个 lambda 函数的 i,是最后调用的时候,也就是 f(2) 时才到外层作用域找它的值的,此时找到的 i 的值就是里面 for 循环结束时的 i 的值。CASE3 则是一开始定义、添加的时候就给 i 赋好了初值。CASE2 则是因为 map 每次迭代的时候都会将一个可迭代对象的元素传给了 i,所以 CASE2 里面的每个 lambda 函数的 i 也是各有各的值的。

像这种 lambda 的自由参数的问题的话,如果你不是故意这么做的话,还是转为默认参数的好:

fs = [lambda x: x i for i in range(3)]print([f(2) for f in fs])fs = [lambda x, i=i: x i for i in range(3)]print([f(2) for f in fs])

[4, 4, 4][2, 3, 4]

另外,就是列表解析里面的作用域是一个全新的作用域,和普通的 for 循环则有所不同:

#---CASE4fs = [lambda j:i*j for i in range(3)]print([f(2) for f in fs])i = 4print([f(2) for f in fs])#---CASE5fs = []for i in range(3):fs.append(lambda j:i*j)print([f(2) for f in fs])i = 4print([f(2) for f in fs])

[10, 10, 10][10, 10, 10][10, 10, 10][8, 8, 8]

4.使用场景
  • 装饰器
  • 惰性求值,比较常见的是在数据库访问的时候,可参考 Django 的 queryset 的实现
  • 需要对某个函数的参数提前赋值的情况;当然也可以使用 functools.parial 的偏函数:functools.partial(func, *args, **kw),返回一个 partial 函数对象。

# y = a*xb, a 和 b 可能只出现一次, x 会出现多次def line(a, b, x):return a*xbprint(line(3, 4, 5))print(line(3, 4, 6))print(line(7, 4, 5))print(line(7, 4, 6))# 2.使用闭包def line(a, b):def value(x):return a*xbreturn value# y = 3x4line1 = line(3, 4)print(line1(5))print(line1(6))print(line1(7))# y = 9x7line2 = line(9, 7)print(line2(5))print(line2(6))print(line2(7))# 3.使用 functools.partial 偏函数from functools import partialline3 = partial(line, 3)print(line3)# functools.partial(<function line at 0x011237C8>, 3)print(line3(4, 5))line4 = partial(line, 3, 4)print(line4(5))print(line4(6))print(line4(7))line5 = partial(line, 9, 7)print(line5(5))print(line5(6))print(line5(7))

简单总结 functools.partial 的作用就是:其能把一个函数的某些参数给固定住(也就是设置默认值),并返回一个新的函数,调用这个新函数会更简单。

    推荐阅读
  • 突触名词解释(突触是什么意思)

    突触名词解释突触是指一个神经元的冲动传到另一个神经元或传到另一细胞间的相互接触的结构。突触是神经元之间在功能上发生联系的部位,也是信息传递的关键部位。在光学显微镜下,可以看到一个神经元的轴突末梢经过多次分支,最后每一小支的末端膨大呈杯状或球状,叫做突触小体。这些突触小体可以与多个神经元的细胞体或树突相接触,形成突触。从电子显微镜下观察,可以看到,这种突触是由突触前膜、突触间隙和突触后膜三部分构成。

  • 《守望先锋》对战局影响大招top一览 守望先锋对局战绩

    今天小编要为大家带来的是玩家“黑呦酱”分享的《守望先锋》对战局影响大招top一览,感兴趣的玩家赶紧一起来看看吧!守望先锋大招分为四类,控制类,自身BUFF类,辅助类以及伤害类,由于伤害类大部分使用大招时,本体无法进行有效杀伤,且控制类及自身BUFF类需要其他技能的配合,so,此间因素也要加入考量。

  • 运动后喝黑咖啡还能燃脂吗 运动时喝黑咖啡会加快燃脂吗?

    2、运动过程中身体脂肪会加速燃烧,从而具有一定减肥作用;而黑咖啡热量比较小,加上其中含有大量的咖啡因以及维生素、纤维素物质,适量喝可以促进人体肠胃蠕动,加速脂肪代谢分解,对减肥具有促进作用。

  • 斯威汽车质量怎么样(斯威质量好不好)

    2018年6月起,斯威“品质特工队”以四大火炉的重庆作为起点,途径海南、吐鲁番、格尔木三地,历时近一年进行了数十万公里极限环境适应性试验。极端干燥高温环境下,常见车内温度往往会狂飙到60℃以上,而在斯威G01的车厢里,却始终能够保持清新凉爽的状态。一整套严酷考验下来,斯威G01的性能表现完全得以充分认证。这样一算,斯威G01差不多完成了近百万公里的专业级严酷考验。

  • 春天兰花怎么养 春天兰花怎么养浇水

    白墨兰花哪个品种最好白墨兰花是墨兰的珍贵变异品种假鳞茎椭圆形,已有数百年栽培历史,流传至今,不下十数个品种,它叶色莹润、体态优雅、幽香静远、且抗病,白墨兰花比较好的品种一般分企剑和软剑两个品系。什么兰花开花最香兰花品种很多,按花香来排,在兰花界春兰居首,惠兰次之,随后便是建兰、墨兰和寒兰,春兰的花香味最正宗,持久性也极强。

  • 奔驰e300l前进挡总共有几个(你看了奔驰22款E300L升级这套原厂HUD抬头显示效果觉得怎么样)

    从行车安全的角度来考虑,加装一台HUD是非常有必要的。HUD的全称是HeadUpDisplay,中文翻译过来就是抬头显示器。今天星骏汇小陈通过以上的产品配件图了解,我们看到这台奔驰22款E300L升级HUD抬头显示所需要更换的配件有,抬显仪器,高配仪表盖板,高配仪表电脑,雨量传感器,空调管升级HUD抬头显示把仪表台上的那一块盖板换掉,换成高配的预留好显示器孔位的盖板,装上显示器,从而使仪表显示的内容投射到挡风玻璃上面。

  • 儿童葫芦丝表演(通城千人共奏葫芦丝)

    儿童葫芦丝表演香城都市报讯 10月27日,通城县隽水中学参加湖北省“黄鹤杯”美育节节目视频录制现场,七、八年级千名学生,同奏乐曲《龙的传人》。该校相关负责人介绍,本学期,每天下午预备铃响5分钟,七、八年级各班集体合奏葫芦丝。丝竹声声,已渐成校园一道靓丽的风景线。近年来,该校贯彻落实社会主义核心价值观,注重未成年人思想道德建设,坚持开设中华传统和特色民族特色教育课程,促进学生“德智体”全面发展。

  • 鸡娃时代孩子的成长之道(与其1岁就开始鸡娃)

    出生时大脑发育已经完成25%,1岁完成了50%,3岁完成了60%,6岁达到90%。现在小学虽然是零基础入学,取消了统一考试,但是它对学生的要求并没有降低。吃够了佛系养娃的亏,橙子家的老二断然不肯再佛系养了。北京卫视于2018年摄制的纪录片《起跑线》中,有一个7岁的北京女孩令人印象深刻。她的家庭,在北京三环内有一套房,一辆车。妈妈认为,孩子从小培养兴趣,靠的是父母的指引。

  • 环氧树脂的作用与用途(环氧树脂有什么作用与用途)

    环氧树脂的作用与用途具有优良的物理和电绝缘性能,强度高、收缩性低,耐腐蚀以及有高绝缘的优势,所以被称为万能胶。电器、电机绝缘封装件的浇注。从常压浇注、真空浇注已发展到自动压力凝胶成型。长时间接触胶水时,有人会有细微的皮肤过敏和细微瘙痒疼痛的情况,建议在运用时戴上防护手套,如果出现了这样的情况,需要用酒精擦洗,然后用清水冲洗干净。

  • 明月曾照江东寒剧情(明月曾照江东寒剧情介绍)

    明月曾照江东寒剧情剧情简介:美少女战清泓是武林副盟主战破敌之女,从小被父亲禁止涉及江湖事。十年一期的武林大会即将来临,战清泓瞒着家人偷跑下山,立志夺取武林盟主之位。战清泓与温宥也开始互生情愫,奈何最终被世俗礼法所阻碍。与此同时,江湖上风起云涌,战清泓发现自己自幼背诵的家训竟是人人趋之若鹜的第一神功《鹤羽剑法》。