Python闭包

news2025/7/19 11:12:33

目录

变量作用域规则(LEGB规则)

闭包

闭包的用途

 (一)读取函数内部的变量

 (二)让函数内部的局部变量始终保持在内存中

(三)总结


变量作用域规则(LEGB规则)

在下面的实例中,我们定义一个测试函数,它读取两个变量的值:一个是局部变量a的值,是函数的参数,一个是变量b的值,这个函数没有定义它.

一个函数,读取一个局部变量和一个全局变量.

 出现错误并不奇怪,在上面的例子中,如果先给全局变量b赋值,也就是定义它,然后再调用f1,那它就不会出错. 

 下面看一个可能让你意想不到的例子,前两行代码与上述例子一样,然后为b赋值,再打印它的值,可是在赋值之前,第二个print失败了.

 注意:首先输出了3,这表明print(a)语句执行了,但是第二个print语句没有执行,一开始我个人觉得会打印6出来,因为有个全局变量b,而且是在print(b)之后为局部变量b赋值的,可事实是,python编译函数时,它判断b是局部变量,因为在函数内部给它赋值了.生成的字节码证实了这种判断,python会尝试从本地环境中获取b,而后调用f2(3)时,f2的定义体会获取并打印局部变量a的值,但是尝试获取局部变量b的值时,发现b没有绑定值.

如果在函数中赋值,想让解释器把b当作全局变量,要使用global声明:

 在python中的作用域规则只需要记住LEGB规则即可,L->local,代表局部作用域,E->Enclosed,代表嵌套函数的外部函数作用域,G->global,代表全局作用域,B->Built in代表的是内置函数的关键字.

其中,局部作用域是最高的级别,当L和G冲突时,是优先认为是局部变量,除非你使用关键字global声明,当L和E冲突时,也是优先认定是局部变量,就是内部嵌套函数的变量,除非你使用关键字nonlocal声明,该变量就是嵌套函数外部函数作用域的变量.为了形象记忆,可以记为LEG+B,腿你不爱吗(-_-)

闭包

可以理解为工厂函数,来料加工.

 

但是有一种方法除外,那就是在函数的内部,再定义一个函数。 

def f1():
    n=999
    def f2():
        print(n)
    
    return f2

result = f1()
result()

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是开头说到的,Python语言特有的作用域搜索顺序。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗?

def f1():
    n=999
    def f2():
        print(n)
    
    return f2

result = f1()
result()

上一部分代码中的f2函数,就是闭包。

在上面的实例中,有一个外层函数的局部变量 n,有一个内层函数 f2,f2 里面可以访问到 n 变量,那这f2就是一个闭包。

下面再看一下维基百科的严谨定义:

在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性,(记忆功能)

 上面这段话实际上解释了闭包的一个定义和两个作用:

  • 定义:闭包就是能够读取外部函数内的变量的函数。(前面已经讲解过)
  • 作用1:闭包是将外层函数内的局部变量和外层函数的外部连接起来的一座桥梁。(下一部分讲解)
  • 作用2:将外层函数的变量持久地保存在内存中。(下一部分讲解)

支持将函数当成对象使用的编程语言,一般都支持闭包。比如Python, JavaScript。

python中一切皆是对象.

闭包的用途

闭包可以用在许多地方。

维基百科的定义中已经提到的它的两个用处:① 可以读取函数内部的变量,②让这些变量的值始终保持在内存中。

 (一)读取函数内部的变量

在第一部分中,我们讲到,有时候会为了保证命名空间的干净而把一些变量隐藏到函数内部,作为局部变量。但是由于Python中作用域的搜索顺序,函数内的变量不会被函数外的代码读取到。

如果这时候想要函数外部的代码能够读取函数内部的变量,那么就可以使用闭包。

闭包存在的意义就是它夹带了外部变量(私货),如果它不夹带私货,它和普通的函数就没有任何区别。同一个的函数夹带了不同的私货,就实现了不同的功能。
其实你也可以这么理解,闭包和面向接口编程的概念很像,可以把闭包理解成轻量级的接口封装。

 (二)让函数内部的局部变量始终保持在内存中

怎么来理解这句话呢?一般来说,函数内部的局部变量在这个函数运行完以后,就会被Python的垃圾回收机制从内存中清除掉。如果我们希望这个局部变量能够长久的保存在内存中,那么就可以用闭包来实现这个功能。

以一个类似棋盘游戏的例子来说明。假设棋盘大小为50*50,左上角为坐标系原点(0,0),我需要一个函数,接收2个参数,分别为方向(direction),步长(step),该函数控制棋子的运动。 这里需要说明的是,每次运动的起点都是上次运动结束的终点。(小甲鱼用的是乌龟运动的例子)

def create(pos=[0,0]):
    
    def go(direction, step):
        new_x = pos[0]+direction[0]*step
        new_y = pos[1]+direction[1]*step
        
        pos[0] = new_x
        pos[1] = new_y
        
        return pos
    
    
    return go

player = create()
print(player([1,0],10))
print(player([0,1],20))
print(player([-1,0],10))

在这段代码中,player实际上就是闭包go函数的一个实例对象。

它一共运行了三次,第一次是沿X轴前进了10来到[10,0],第二次是沿Y轴前进了20来到 [10, 20],,第三次是反方向沿X轴退了10来到[0, 20]。

这证明了,函数create中的局部变量pos一直保存在内存中,并没有在create调用后被自动清除。

为什么会这样呢?原因就在于create是go的父函数,而go被赋给了一个全局变量,这导致go始终在内存中,而go的存在依赖于create,因此create也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这个时候,闭包使得函数的实例对象的内部变量,变得很像一个的实例对象的属性,可以一直保存在内存中,并不断的对其进行运算。

(三)总结

  • 局部变量无法共享和长久的保存,而全局变量可能造成变量污染,闭包既可以长久的保存变量又不会造成全局污染。
  • 闭包使得函数内局部变量的值始终保持在内存中,不会在外层函数调用后被自动清除。
  • 当外层函数返回了内层函数后,外层函数的局部变量还被内层函数引用
  • 带参数的装饰器,那么一般都会生成闭包。
  • 闭包在爬虫以及web应用中都有很广泛的应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/36915.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

112. 路径总和

文章目录2.示例3.答案①递归②BFS③DFS给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返…

智慧电力解决方案-最新全套文件

智慧电力解决方案-最新全套文件一、建设背景二、建设思路三、建设方案四、获取 - 智慧电力全套最新解决方案合集一、建设背景 电力能源是支撑整个社会运行的最重要基础体系之一。伴随着经济和社会生活的发展,人们对电力需求不断增长,同时随着能源需求日…

docker 安装 portainer 来管理容器 (记录 1)

docker 安装 portainer 来管理容器 (记录 1) 初docker 安装 portainer前置条件:安装docker下载镜像 portainer部署容器配置进入监控初 希望能写一些简单的教程和案例分享给需要的人 docker 安装 portainer 前置条件:安装docker…

【JavaSE】异常处理

文章目录什么是异常异常的分类异常的处理流程throw异常的抛出throws 和 try-catch捕获异常finally自定义异常类什么是异常 在生活中我们头疼脑热都是身体出现的异常,在代码中也是一样的,尽管程序员在写代码时已经非常追求完美,但是总会有一些…

【真北读书】弗兰克意义三途径,让你人生的意义不漂移

维克多弗兰克,意义疗法创始人,出生于奥地利维也纳一个贫困的犹太人家庭。弗兰克15岁时质疑化学老师说的生命只是化学燃料,开始思考生命的意义,曾学习弗洛伊德、阿德勒、海德格尔和尼采,在被囚禁之前,已形成…

终于有人把大数据的相关知识讲全了(大数据基础框架、数据库、大数据分析分布式技术),从入门到进阶全部涵盖了!

前言 经常有初学者在博客和微信问我,自己想往大数据方向发展,该学哪些技术,学习路线是什么样的,觉得大数据很火,就业很好,薪资很高。如果自己很迷茫,为了这些原因想往大数据方向发展&#xff0…

用VB设计年级人员管理系统

目 录 摘 要 I Abstract II 第一章 绪论 1 1.1 课题研究背景 1 1.2 课题研究目的和任务 1 1.3 课题研究的意义 2 1.4 论文的主要内容和章节安排 2 第二章 运行环境开发工具 3 2.1 运行环境 3 2.2 开发工具简介 3 2.2.1 VB6.0基础 3 2.2.2 SQL Server2000数据库基础 3 第三章 需…

【HDU No. 2874】 城市之间的联系 Connections between cities

【HDU No. 2874】 城市之间的联系 Connections between cities 杭电OJ 题目地址 【题意】 由于大部分道路在战争期间已被完全摧毁,所以两个城市之间可能没有路径,也没有环。 已知道路状况,想知道任意两个城市之间是否存在路径。若答案是肯…

11.一键分析你的上网行为(web页面可视化)

## 一键分析你的上网行为, 看看你平时上网都在干嘛? ## Chrome浏览器历史记录文件可视化### 简介**想看看你最近一年都在干嘛?看看你平时上网是在摸鱼还是认真工作?想写年度汇报总结,但是苦于没有数据?现在,它来了。*…

国外顶尖程序员手写,402页汉译版微服务与事件驱动架构开发手册

为什么用事件驱动型微服务? Marshall McLuhan认为,影响人类并给社会带来根本性变革的不是媒介的内容,而是与媒介的互动过程。在我们的集体参与下,报纸、广播、电视、互联网、即时通信和社交媒体改变了人类的互动方式以及社会结构…

JDK17新特性

一.JEP 409: Sealed Classes 1.1简介 官网链接sealed class 从如下官网的简介与描述可以看出,这个新特性的目的是为了限制类与接口的 被继承与实现,比如说我有个 A类,那么我现在限定只有 B,C,D三个类可以继承 那么就需…

DRM架构介绍(一)

1、 DRM简介(Direct Rendering Manager)传统linux显示设备驱动开发时,通常使用FB驱动架构,随着显卡性能升级:显示覆盖(菜单层级)、GPU加速、硬件光标,传统FB架构无法很好支持&#x…

activiti-bpmn-converter

activiti-bpmn-converter目录概述需求:设计思路实现思路分析1.BpmnXMLConstants2.BpmnXMLConverter3.StartEventXMLConverter4.EndEventXMLConverter参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full bu…

C++调用OpenCV实现图像平滑处理

1 前言 图像的平滑处理,也叫做模糊处理,是在尽量保留原始图像信息的情况下,抑制或去除图像内的噪声、降低细节层次信息等一系列过程。是一种操作简单但使用频率很高的图像处理方法。由于实际的图像索引方式,与我们常用的x和y轴的…

2022年 SecXOps 安全智能分析技术白皮书 学习笔记 免费下载地址

核心能力 为了加快安全分析能力更全面、更深入的自动化 ,SecXOps 的目标在于创建一个集成的用于 Security 的 XOps 实践,提升安全分析的场景覆盖率和运营效率。SecXOps 技术并不 015 SecXOps 技术体系 是 Ops 技术在安全领域的简单加和,SecXO…

postman拦截浏览器请求

postman可以去浏览器请求进行拦截,并将请求存放至集合。 原理 postman内部有捕获http请求流量的代理; postman app 监听这客户端程序或者设备发出的调用请求;postman 代理端会捕获到请求并把它转发到服务器;服务器将响应返回给p…

双非大学改考408,软件工程专业考研报考人数较少!

南昌航空大学是一所双非大学,位于江西省南昌市。南昌航空大学计算机学科评估没有,软件工程学科评估C,计算机实力在双非大学中也并不算强。南昌航空大学今年计算机考研大变化,软件学院的所有专业(包括:软件工…

虚拟化性能优化系列-numatune

numatune是什么 numatune是libvirt的一个参数&#xff0c;可以用在numa架构的host上&#xff0c;以控制子机的内存访问策略。 使用方法如下&#xff0c;参考libvirt文档 <domain> ... <numatune> <memory mode"strict" nodeset"1-4,^3"/&…

多线程(1)

多线程 前言 &#xff1a; 上文主要了解到了进程&#xff0c; 那么为啥需要引入进程呢&#xff1f;   或者说为啥要有进程呢&#xff1f; 其实这里最主要的目的是为了解决 并发编程 这样的问题。 了解 &#xff1a;   这里 cpu 进入了多核心的时代&#xff0c;想要进一步提…

解决:给 VSCode 手动添加(解压压缩包)相关插件的问题

1、一般的添加方式为&#xff1a; 在 VSCode 里面扩展程序里面直接搜索添加即可&#xff1b; 2、如何手动添加一个 VSCode 相关插件&#xff1f; 其一、首先把相关文件解压&#xff0c;解压成一个文件夹&#xff1b; 其二、找到 VSCode 的 extensions 的地址并把 A 所述文件…