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

Python中闭包和自由变量的使用与注意事项

2024-04-02 19:04:59 165人浏览 安东尼

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

摘要

目录1.定义2.nonlocal 关键字3.注意事项4.使用场景总结1.定义 在函数内部再定义一个函数,并且这个函数用到了外部函数的变量(LEGB),最后返回新建函数的函数名索引,那

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())  # 6
print(num5())  # 7
print(num10())  # 11
print(num10())  # 12

# 如果这个函数仅仅是嵌套函数,那么它的 __closure__ 应该是 None
print(num5.__closure__)  # (<cell at 0x0163FE30: list object at 0x01514A80>,)
print(num5.__closure__[0].cell_contents)  # 7
print(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_one

num5 = 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_one

num5 = 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 += 1
UnboundLocalError: 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] += 1
TypeError: '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_one

num5 = 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 spam
After global assignment: nonlocal spam
In global scope: global spam

3.注意事项

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

#---CASE1
fs = [lambda j:i*j for i in range(3)]
print([f(2) for f in fs])

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

#---CASE3
fs = [(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 循环则有所不同:

#---CASE4
fs = [lambda j:i*j for i in range(3)]
print([f(2) for f in fs])

i = 4
print([f(2) for f in fs])

#---CASE5
fs = []

for i in range(3):
    fs.append(lambda j:i*j)
print([f(2) for f in fs])

i = 4
print([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 + b

print(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 + 4
line1 = line(3, 4)
print(line1(5))
print(line1(6))
print(line1(7))

# y = 9x + 7
line2 = line(9, 7)
print(line2(5))
print(line2(6))
print(line2(7))

# 3.使用 functools.partial 偏函数
from functools import partial

line3 = 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/141374.html(转载时请注明来源链接)

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

猜你喜欢
  • Python中闭包和自由变量的使用与注意事项
    目录1.定义2.nonlocal 关键字3.注意事项4.使用场景总结1.定义 在函数内部再定义一个函数,并且这个函数用到了外部函数的变量(LEGB),最后返回新建函数的函数名索引,那...
    99+
    2024-04-02
  • Python中闭包和自由变量的使用方法与注意事项是什么
    这篇文章主要为大家展示了“Python中闭包和自由变量的使用方法与注意事项是什么”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Python中闭包和自由变量的使用方法与注意事项是什么”这篇文章吧。...
    99+
    2023-06-29
  • python闭包使用要注意哪些事项
    在使用Python闭包时,需要注意以下几个事项: 理解闭包的概念:闭包是指一个函数内部定义的函数,并且该内部函数引用了外部函数的...
    99+
    2023-10-27
    python
  • Python闭包的两个注意事项(推荐)
    什么是闭包? 简单说,闭包就是根据不同的配置信息得到不同的结果。 再来看看专业的解释:闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自...
    99+
    2022-06-04
    注意事项 两个 Python
  • Go语言函数闭包的用法和注意事项
    闭包使函数可以访问其创建范围之外的变量。在 go 中,内层函数可以访问外层函数作用域中的所有变量,包括:状态管理: 闭包可用来管理长期状态,即使函数已返回。事件处理: 闭包可创建事件处理...
    99+
    2024-04-13
    go语言 闭包 作用域
  • Python中的标识符规则和变量命名注意事项
    Python中的命名规则及变量命名的注意事项 Python是一种简单易学、功能强大的编程语言,良好的命名规范和规则可以使代码更易读、易懂,并提高代码的可维护性。本文将介绍Python中的命名规则及变量命名的注意事项,并给出具体的...
    99+
    2024-01-20
    命名规则:变量名
  • C/C++中的静态变量注意事项
    前言 C/C++中的静态变量,相信大多数人都用过,但你很可能用错了,包括你现在所在的项目中都可能埋着这个坑,不信我们往下看! 正文 我们先来看一段大家常写的代码,很简单,这段代码没啥...
    99+
    2024-04-02
  • python中类变量与成员变量的使用注意点总结
    前言 最近在用python写一个项目,发现一个很恶心的bug,就是同由一个类生成的两个实例之间的数据竟然会相互影响,这让我非常不解。后来联想到java的类有类变量也有实例变量,因此翻阅了相关资料,发现pyt...
    99+
    2022-06-04
    变量 成员 python
  • 在java中使用变量时需要注意哪些事项
    本篇文章为大家展示了在java中使用变量时需要注意哪些事项,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。1、说明每个变量都有类型,类型可以是基本类型,也可以是引用类型。变量名必须是合法的标识符。变量...
    99+
    2023-06-15
  • Python多线程使用和注意事项
    多线程   基本实现: 第一种,函数方式 # -*- coding:utf-8 -*- import thread import time     def print_time(threadName, delay):     count...
    99+
    2023-01-30
    多线程 注意事项 Python
  • Python中Enum使用的几点注意事项
    Enum 是个类 所以 基本的类操作都可以用 也就是我们可以添加自己的方法 class Mood(Enum): FUNKY = 1 HAPPY = 3 def...
    99+
    2024-04-02
  • python executemany的使用及注意事项
    使用executemany对数据进行批量插入的话,要注意一下事项: #coding:utf8 conn = MySQLdb.connect(host = “localhost”, user = “roo...
    99+
    2022-06-04
    注意事项 python executemany
  • Python中Enum使用的注意事项有哪些
    小编给大家分享一下Python中Enum使用的注意事项有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!Enum 是个类所以基本的类操作都可以用也就是我们可以添...
    99+
    2023-06-29
  • 使用golang-unsafe包的注意事项及说明
    目录总结(详细的内容可以往下看)详细内容总结基于golang 15.5 总结(详细的内容可以往下看) 1.不能使用unsafe包里的ArbitraryType类型 2.Pointer...
    99+
    2023-02-10
    golang-unsafe包 golang unsafe 使用golang-unsafe包
  • MySQL的Jar包使用指南及注意事项
    MySQL的Jar包使用指南及注意事项 MySQL是一种常用的关系型数据库管理系统,许多Java项目都会使用MySQL作为数据存储的后端。在Java项目中,要与MySQL数据库进行交互...
    99+
    2024-03-01
    mysql jar包 注意事项 sql语句 防止sql注入
  • Java中Process类的使用与注意事项说明
    目录Process类的使用与注意事项说明1、在项目开发中2、在这里就需要认识一下process类3、来说说今天业务需求[waitfor()]:4、前不久遇到一个奇怪的问题就是ajax...
    99+
    2024-04-02
  • Springboot中@RequestBody注解使用的注意事项
    这篇文章将为大家详细讲解有关Springboot中@RequestBody注解使用的注意事项,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。@RequestBody注解踩坑@RequestBody json...
    99+
    2023-06-29
  • Mybatis-Plus分页的使用与注意事项
    目录1.写个Mybatis-plus配置类:2.写接口测试3.注意4.如果你还有查询条件1.Lambda表达式2.普通查询总结1.写个Mybatis-plus配置类: 是通过拦截器实...
    99+
    2024-04-02
  • 在python中使用可变参数时需要注意哪些事项
    在python中使用可变参数时需要注意哪些事项?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。python主要应用领域有哪些1、云计算,典型应用OpenStack...
    99+
    2023-06-14
  • python 闭包中引用的变量值变更问题
    python的闭包当内层函数引用外层函数的局部变量时,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变。如下:def count():     fs = []     for i in range(1, 4):         de...
    99+
    2023-01-31
    包中 变量值 python
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作