文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Python中闭包和自由变量的使用方法与注意事项是什么

2023-06-29 09:53

关注

这篇文章主要为大家展示了“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  # number        def add_one():        cnt += 1        return cnt    return add_onenum5 = counter(5)print(num5.__closure__)print(num5.__code__.co_freevars)print(num5())----------------------------------------------------------------------------def counter(FIRST=0):    cnt = (FIRST,)  # tuple        def add_one():        cnt[0] += 1        return 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_one    cnt += 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_one    cnt[0] += 1TypeError: 'tuple' object does not support item assignment

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

这是因为 Python 中并没有要求先声明一个变量才能使用它,Python 解释器认为:在函数体内,只要对一个变量进行赋值操作,那么这个变量就是局部变量。
Python的模块代码执行之前,并不会经过预编译,模块内的函数体代码在运行前会经过预编译,因此不管变量名的绑定发生在作用域的那个位置,都能被编译器知道。

而 cnt += 1 相当于 cnt = cnt + 1,对 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  # number        def add_one():        nonlocal cnt        cnt += 1        return cnt    return 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 spam        spam = "nonlocal spam"    def do_global():        global spam        spam = "global spam"    do_nonlocal()    print("After nonlocal assignment:", spam)  # nonlocal spam     do_global()    print("After global assignment:", spam)  # nonlocal spam    scope_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.使用场景

# y = a*x + b, a 和 b 可能只出现一次, x 会出现多次def line(a, b, x):    return a*x + bprint(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*x + b    return value# y = 3x + 4line1 = line(3, 4)print(line1(5))print(line1(6))print(line1(7))# y = 9x + 7line2 = 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的作用就是:其能把一个函数的某些参数给固定住(也就是设置默认值),并返回一个新的函数,调用这个新函数会更简单。

以上是“Python中闭包和自由变量的使用方法与注意事项是什么”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注编程网行业资讯频道!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯