返回顶部
首页 > 资讯 > 后端开发 > Python >Python中闭包和自由变量的使用方法与注意事项是什么
  • 713
分享到

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

2023-06-29 09:06:16 713人浏览 八月长安

Python 官方文档:入门教程 => 点击学习

摘要

这篇文章主要为大家展示了“python中闭包和自由变量的使用方法与注意事项是什么”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Python中闭包和自由变量的使用方法与注意事项是什么”这篇文章吧。

这篇文章主要为大家展示了“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.使用场景

  • 装饰器

  • 惰性求值,比较常见的是在数据库访问的时候,可参考 Django 的 queryset 的实现

  • 需要对某个函数的参数提前赋值的情况;当然也可以使用 functools.parial 的偏函数:functools.partial(func, *args, **kw),返回一个 partial 函数对象。

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

--结束END--

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

本文链接: https://lsjlt.com/news/324024.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Python中闭包和自由变量的使用方法与注意事项是什么
    这篇文章主要为大家展示了“Python中闭包和自由变量的使用方法与注意事项是什么”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Python中闭包和自由变量的使用方法与注意事项是什么”这篇文章吧。...
    99+
    2023-06-29
  • Python中闭包和自由变量的使用与注意事项
    目录1.定义2.nonlocal 关键字3.注意事项4.使用场景总结1.定义 在函数内部再定义一个函数,并且这个函数用到了外部函数的变量(LEGB),最后返回新建函数的函数名索引,那...
    99+
    2024-04-02
  • Go中函数的使用方法与注意事项是什么
    今天小编给大家分享一下Go中函数的使用方法与注意事项是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。细节汇总函数的形参列...
    99+
    2023-07-04
  • golang函数注释的使用方法和注意事项是什么
    这篇文章主要介绍了golang函数注释的使用方法和注意事项是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇golang函数注释的使用方法和注意事项是什么文章都会有所收获,下面我们一起来看看吧。一、函数注释概...
    99+
    2023-07-05
  • Assert.assertEquals的使用方法及注意事项是什么
    这篇文章主要介绍了Assert.assertEquals的使用方法及注意事项是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Assert.assertEquals的使用方法及注意事项是什么文章都会有所收获,...
    99+
    2023-06-30
  • React中style的使用方法及注意事项是什么
    这篇“React中style的使用方法及注意事项是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“React中style的...
    99+
    2023-07-05
  • php urlencode转中文的方法和注意事项是什么
    本篇内容主要讲解“php urlencode转中文的方法和注意事项是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“php urlencode转中文的方法和注意事项是什么”吧!一、urlenc...
    99+
    2023-07-05
  • localStorage的用法及使用注意事项是什么
    这篇文章主要讲解了“localStorage的用法及使用注意事项是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“localStorage的用法及使用注意事项是什么”吧!localStor...
    99+
    2023-06-29
  • Mybatis-Plus分页的使用与注意事项是什么
    这篇文章主要介绍“Mybatis-Plus分页的使用与注意事项是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Mybatis-Plus分页的使用与注意事项是什么”文章能帮助大家解决问题。1.写个...
    99+
    2023-06-30
  • Python中深浅拷贝的使用及注意事项是什么
    这篇文章主要讲解了“Python中深浅拷贝的使用及注意事项是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python中深浅拷贝的使用及注意事项是什么”吧!一、Python深浅拷贝概念在...
    99+
    2023-07-05
  • PHP类方法前加@符号的作用与注意事项是什么
    这篇文章主要介绍“PHP类方法前加@符号的作用与注意事项是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“PHP类方法前加@符号的作用与注意事项是什么”文章能帮助大家解决问题。在PHP中,我们可以...
    99+
    2023-07-05
  • sql变量创建和使用的方法是什么
    在SQL中,可以使用变量来存储数据和进行操作。变量的创建和使用方法取决于所使用的数据库管理系统。以下是在一些常见的数据库管理系统中创...
    99+
    2024-04-09
    sql
  • 浅谈java中null是什么,以及使用中要注意的事项
    1.null既不是对象也不是一种类型,它仅是一种特殊的值,你可以将其赋予任何引用类型,你也可以将null转化成任何类型,例如:Integer i=null;Float f=null;String s=null;但是不能把null赋值给基本类...
    99+
    2023-05-31
    java null ava
  • python中变量声明和赋值的方法是什么
    在Python中,变量的声明和赋值是同时进行的,可以使用以下方式进行变量声明和赋值:1. 直接赋值:通过使用等号(=)来将一个值赋给...
    99+
    2023-08-08
    python
  • 在 PHP 中使用容器加载数组:你需要注意的事项是什么?
    在 PHP 开发过程中,加载数组是一个非常常见的操作,而容器则是一种非常有用的工具,可以用于在应用程序中组织和管理对象。使用容器加载数组可以使得代码更加简洁和易于维护。但是,在使用容器加载数组时,需要注意一些事项,以确保代码的正确性和性能...
    99+
    2023-08-22
    数组 load 容器
  • Vue中的路由使用和多种守卫方法是什么
    今天小编给大家分享一下Vue中的路由使用和多种守卫方法是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。1.vue-rou...
    99+
    2023-07-05
  • Python中的日志处理和调试技巧在实际开发中的应用场景和注意事项是什么?
    Python中的日志处理和调试技巧在实际开发中的应用场景和注意事项在软件开发中,确保代码的正确性和可靠性是至关重要的。为了实现这一目标,日志处理和调试技巧是不可或缺的工具之一。Python作为一门广泛应用于各个领域的编程语言,提供了许多方便...
    99+
    2023-10-27
    应用场景 日志处理 调试技巧 注意事项
  • Python中chinesecalendar安装和使用的方法是什么
    基础用法chinesecalendar的简介该库是判断某年某月某一天是不是工作日/节假日。支持将春节延长,时间跨度为2004年至2023年,其中包括2020年。由于次年的节假日安排,取决于国务院发布的日程。 所以本项目一般会在国务院更新以后...
    99+
    2023-05-19
    Python chinesecalendar
  • ubuntu中snap包安装、更新删除与使用的方法是什么
    这篇“ubuntu中snap包安装、更新删除与使用的方法是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“ubuntu中s...
    99+
    2023-07-04
  • Unix系统中的Python API和数组:使用它们的最佳方法是什么?
    Unix系统中的Python API和数组是非常强大的工具,可以帮助我们更高效地处理数据和系统操作。在本文中,我们将介绍如何使用Unix系统中的Python API和数组,并探讨它们的最佳用法。 一、Unix系统中的Python API ...
    99+
    2023-08-06
    api 数组 unix
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作