深入理解 python 虚拟机:原来虚拟机是这么实现闭包的
深入理解 python 虚拟机:原来虚拟机是这么实现闭包的
在本篇文章当中主要从虚拟机层面讨论函数闭包是如何实现的,当能够从设计者的层面去理解闭包就再也不用死记硬背一些闭包的概念了,因为如果你理解闭包的设计原理之后,这些都是非常自然的。
根据 wiki 的描述,a closure is a record storing a function together with an environment。所谓闭包就是将函数和环境存储在一起的记录。这里有三个重点一个是函数,一个是环境(简单说来就是程序当中变量),最后一个需要将两者组合在一起所形成的东西,才叫做闭包。
Python 中的闭包
我们现在用一种更加直观的方式描述一下闭包:闭包是指在函数内部定义的函数,它可以访问外部函数的局部变量,并且可以在外部函数执行完后继续使用这些变量。这是因为闭包在创建时会捕获其所在作用域的变量,然后保持对这些变量的引用。下面是一个详细的Python闭包示例:
def outer_function(x):# 外部函数定义了一个局部变量 xdef inner_function(y):# 内部函数可以访问外部函数的局部变量 xreturn x + y# 外部函数返回内部函数的引用,形成闭包return inner_function# 创建两个闭包实例,分别使用不同的 x 值
closure1 = outer_function(10)
closure2 = outer_function(20)# 调用闭包,它们仍然可以访问其所在外部函数的 x 变量
result1 = closure1(5) # 计算 10 + 5,结果是 15
result2 = closure2(5) # 计算 20 + 5,结果是 25print(result1)
print(result2)
在上面的示例中,outer_function
是外部函数,它接受一个参数 x
,然后定义了一个内部函数 inner_function
,它接受另一个参数 y
,并返回 x + y
的结果。当我们调用 outer_function
时,它返回了一个对 inner_function
的引用,形成了一个闭包。这个闭包可以保持对 x
的引用,即使 outer_function
已经执行完毕。
在上面的例子当中 outer_function
的返回值就是闭包,这个闭包包含函数和环境,函数是 inner_function
,环境就是 x
,从程序语义的层面来说返回值是一个闭包,但是如果直接从 Python 层面来看,返回值也是一个函数,现在我们打印两个闭包看一下结果:
>>> print(closure1)
<function outer_function.<locals>.inner_function at 0x102e17a60>
>>> print(closure2)
<function outer_function.<locals>.inner_function at 0x1168bc430>
从上面的输出结果可以看到两个闭包(从 Python 层面来说也是函数)所在的内存地址是不一样的,因此每次调用都会返回一个不同的函数(闭包),因此两个闭包相互不影响。
再来看下面的程序,他们的执行结果是什么?
def outer_function(x):def inner_function(y):nonlocal xx += 1return x + yreturn inner_functionclosure1 = outer_function(10)
closure2 = outer_function(20)result1 = closure1(5)
print(result1)
result1 = closure1(5)
print(result1)
result2 = closure2(5)
print(result2)
输出结果为:
16
17
26
根据上面的分析 closure1 和 closure2 分别是两个不同的闭包,两个闭包的 x 也是各自的 x ,因此前一个闭包的 x 变化并不会影响第二个闭包,所以 result2 的输出结果为 26。
闭包相关的字节码
在正式了解闭包相关的字节码之前我们首先来重新回顾一下 CodeObject 当中的字段:
def outer_function(x):def inner_function(y):nonlocal xx += 1return x + yprint(inner_function.__code__.co_freevars) # ('x',)print(inner_function.__code__.co_cellvars) # ()return inner_functionif __name__ == '__main__':out = outer_function(1)print(outer_function.__code__.co_freevars) # ()print(outer_function.__code__.co_cellvars) # ('x', )
cellvars 表示在其他函数当中会使用本地定义的变量,freevars 表示本地会使用其他函数定义的变量。在上面的例子当中,outer_function 当中的变量 x 会被 inner_function 使用,而cellvars 表示在其他函数当中会使用本地定义的变量,所以 outer_function 的这个字段为 (‘x’, )。如果要了解详细的信息可以参考这篇文章 深入理解 python 虚拟机:字节码灵魂——Code obejct 。
上面的内容我们简要回顾了一下 CodeObject 当中的两个非常重要的字段,这两个字段在进行传递参数的时候非常重要,当我们在进行函数调用的时候,虚拟机会新建一个栈帧,在进行新建栈帧的过程当中,如果发现 co_cellvars 存储的字符串变量也是函数参数的时候,除了会在局部变量当中保存一份参数之外,还会将传递过来的参数保存到栈帧对象的其他位置当中(这里需要注意一下,CodeObject 当中的 co_freevars 保存的是字符串,也就是变量名,栈帧当中保存的是变量名字对应的真实对象,也就是函数参数),这么做的目的是为了方面后面字节码 LOAD_CLOSURE 的操作,因为实际虚拟机存储的是指向对象的指针,因此浪费不了多少空间。
实际在虚拟机的栈帧对象当中 freevars 是一个数组,后续的字节码都是会根据数组下标对这些变量进行操作。
下面我们分析一下和闭包相关的字节码操作
def outer_function(x):def inner_function(y):nonlocal xx += 1return x + yreturn inner_functionif __name__ == '__main__':import disdis.dis(outer_function)
上面的代码回输出 outer_function 和 inner_function 对应的字节码:
2 0 LOAD_CLOSURE 0 (x)2 BUILD_TUPLE 14 LOAD_CONST 1 (<code object inner_function at 0x100757a80, file "closure_bytecode.py", line 2>)6 LOAD_CONST 2 ('outer_function.<locals>.inner_function')8 MAKE_FUNCTION 8 (closure)10 STORE_FAST 1 (inner_function)7 12 LOAD_FAST 1 (inner_function)14 RETURN_VALUEDisassembly of <code object inner_function at 0x100757a80, file "closure_bytecode.py", line 2>:4 0 LOAD_DEREF 0 (x)2 LOAD_CONST 1 (1)4 INPLACE_ADD6 STORE_DEREF 0 (x)5 8 LOAD_DEREF 0 (x)10 LOAD_FAST 0 (y)12 BINARY_ADD14 RETURN_VALUE
我们现在来详细解释一下上面的字节码含义:
- LOAD_CLOSURE:这个就是从栈帧对象当中加载指定下标的 cellvars 变量,在上面的字节码当中就是加载栈帧对象 cellvars 当中下标为 0 的对象,对应的参数就是 x 。也就是将参数 x 加载到栈帧上。
- BUILD_TUPLE:从栈帧当中弹出 oparg (字节码参数) 个参数,并且将这些参数封装成元祖,在上面的程序当中 oparg = 1 。
- LOAD_CONST:加载对应的常量到栈帧当中,这里是会加载两个常量,分别是函数对应的 CodeObject 和函数名。
在执行完上的字节码之后栈帧当中 valuestack 如下所示:
- MAKE_FUNCTION:这条字节码的主要作用是根据上面三个栈里面的对象创建一个函数,其中最重要的字段就是 CodeObject 这里面保存了函数最重要的代码,最下面的元祖就是 inner_function 的 freevars,当虚拟机在创建函数的时候就已经把这个对象保存下来了,然后在创建栈帧的时候会将这个对象保存到栈帧。需要注意的是这里所保存的变量就是函数参数 x,他们是同一个对象。这就使得内部函数每次调用的时候都可以使用参数 x 。
我们再来看一下函数 inner_function 的字节码
- LOAD_DEREF:这个字节码会从栈帧的 freevars 数组当中加载下标为 oparg 的对象,freevars 就是刚刚在创建函数的时候所保存的,也就是 outter_function 传递给 inner_function 的元祖。直观的来说就是将外部函数的 x 加载到 valuestack 当中。
- STORE_DEREF:就是将栈顶的元素弹出,保存到 cellvars 数组对应的下标 (oparg) 当中。
后续的字节码就很简单了,这里不做详细分析了。
如果上面的过程太复杂,我们在这里从整体的角度再叙述一下,简单说来就是当有代码调用 outer_function 的时候,传递进来的参数,会在 outer_function 创建函数 inner_function 的时候当作闭包参数传递给 inner_function,这样 inner_function 就能够使用 outer_function 的参数了,因此这也不难理解,每次我们调用函数 outer_function 都会返回一个新的闭包(实际就是返回的新创建的函数),因为我们每次调用函数 outer_function 时,它都会创建一个新的函数,而这些被创建的函数唯一的区别就是他们的闭包参数不同。这也就解释了再之前的例子当中为什么两个闭包他们互不影响,因为函数 outer_function 创建了两个不同的函数。
总结
在本篇文章当中详细介绍了闭包的使用例子和使用原理,理解闭包最重要的一点就是函数和环境,也就是和函数绑定在一起的变量。当进行函数调用的时候函数就会创建一个新的内部函数,也就是闭包。在虚拟机内部实现闭包主要是通过函数参数传递和函数生成实现的,当执行 MAKE_FUNCTION 创建新函数的时候,会将外部函数的闭包变量 (在文章中就是 x ) 传递给内部函数,然后保存在内部函数当中,之后的每一次调用都是用这个变量,从而实现闭包的效果。
本篇文章是深入理解 python 虚拟机系列文章之一,文章地址:https://github.com/Chang-LeHung/dive-into-cpython
更多精彩内容合集可访问项目:https://github.com/Chang-LeHung/CSCore
关注公众号:一无是处的研究僧,了解更多计算机(Java、Python、计算机系统基础、算法与数据结构)知识。
相关文章:
深入理解 python 虚拟机:原来虚拟机是这么实现闭包的
深入理解 python 虚拟机:原来虚拟机是这么实现闭包的 在本篇文章当中主要从虚拟机层面讨论函数闭包是如何实现的,当能够从设计者的层面去理解闭包就再也不用死记硬背一些闭包的概念了,因为如果你理解闭包的设计原理之后,这些都是…...
【数据结构-哈希表 一】【原地哈希】:缺失的第一个正整数
废话不多说,喊一句号子鼓励自己:程序员永不失业,程序员走向架构!本篇Blog的主题是【原地哈希】,使用【数组】这个基本的数据结构来实现,这个高频题的站点是:CodeTop,筛选条件为&…...
【C++设计模式之迭代器模式】分析及示例
简介 迭代器模式是一种行为型设计模式,它提供了一种顺序访问聚合对象元素的方法,而又不需要暴露聚合对象的内部结构。迭代器模式通过将遍历算法封装在迭代器对象中,可以使得遍历过程更简洁、灵活,并且符合开闭原则。 描述 迭代…...
【代码随想录】LC 27. 移除元素
文章目录 前言一、题目1、原题链接2、题目描述 二、解题报告1、思路分析2、时间复杂度3、代码详解 三、知识风暴 前言 本专栏文章为《代码随想录》书籍的刷题题解以及读书笔记,如有侵权,立即删除。 一、题目 1、原题链接 27. 移除元素 2、题目描述 二、…...
crash工具分析dma设备内存踩踏(一)
背景介绍 我们的客户在利用我们提供的SDK参考方案开发相关产品时,在产品方案上进行一些基础老化测试时,极低概率出现kernel随机panic问题,由于场景复杂,无法单独针对特定模块或功能进行拆解来进行实验排查,只能基于已…...
C#上位机——根据命令发送
C#上位机——根据命令发送 第一步:设置窗口的布局 第二步:设置各个属性 第三步:编写各个模块之间的关系...
BEVFormer代码跑通
1 环境配置 1.1 环境安装 # 1 拉取源码 github加速代理https://ghproxy.com/ git clone https://github.com/fundamentalvision/BEVFormer.git# 2 创建虚拟环境 conda create -n bev python3.8 -y# 3 激活虚拟环境 conda activate bev# 4.1 安装torch,torchvision,torchaud…...
kafka安装
kafka安装 1 kafka概念 1.1 kafka介绍 kafka是最初有Linkedin公司开发的,是一个分布式,分区,多副本,多生产者,多订阅者,基于zookeeper协调的分布式日志系统。具有高吞吐量,可扩展性和可容错性…...
Mac上安装Java的JDK多版本管理软件jEnv
JDK的多版本管理软件主要有以下三种: jEnv jEnv 是一个命令行工具,可以帮助您管理和切换不同版本的 Java 环境。它可以让您在不同的项目之间轻松切换 Java 版本。您可以使用 jenv global 命令设置全局 Java 版本,也可以使用 jenv local 命令…...
linux常见命令以及jdk,tomcat环境搭建
目录 Is pwd cd touch cat echo vim 复制粘贴 mkdir rm cp jdk部署 1. yum list | grep jdk进行查找编辑 2.安装编辑 3.再次确认 4.判断是否安装成功 tomcat安装 1.下载压缩包,把压缩包上传至linux(可能需要yum install lrzsz) 2.解压缩unzip 压缩包名&…...
将表情存入数据库
概念: 表情是一种比较特殊的字符串,为unicode编码,unicode编码要存入数据库一般情况下,是存不了的,有两种解决方式,一种将数据表编码方式改为unicode编码方式,但是这种情况适用于功能刚开始设计…...
H桥级联型五电平三相逆变器Simulink仿真模型
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
后端解决跨域(极速版)
header(Access-Control-Allow-Origin: *); header(Access-Control-Allow-Methods:*); 代表接收全部的请求,"POST,GET"//允许访问的方式 指定域,如http://172.20.0.206//宝塔的域名,注意不是:http://wang.jingyi.icu等…...
数据结构与算法-前缀树
数据结构与算法-前缀树详解 1 何为前缀树 2 前缀树的代码表示及相关操作 1 何为前缀树 前缀树 又称之为字典树,是一种多路查找树,多路树形结构,是哈希树的变种,和hash效率有一拼,是一种用于快速检索的多叉树结构。 性质:不同字符串的相同…...
DirectX12_Windows_GameDevelop_3:Direct3D的初始化
引言 查看龙书时发现,第四章介绍预备知识的代码不太利于学习。因为它不像是LearnOpenGL那样从头开始一步一步教你敲代码,导致你没有一种整体感。如果你把它当作某一块的代码进行学习,你跟着敲会发现,总有几个变量是没有定义的。这…...
基于粒子群优化算法、鲸鱼算法、改进的淘沙骆驼模型算法(PSO/SSA/tGSSA)的微电网优化调度(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
数据分析篇-数据认知分析
一简介 数据认知分析,实际是对数据的整体结构和分布特征进行分析,是对整个数据外在的认识,也是数据分析的第一步。对于数据认知的分析,一般会考虑分散性、位置特性、变量的相关性等,一般会考虑平均数、方差、极差、峰…...
【力扣-每日一题】714. 买卖股票的最佳时机含手续费
class Solution { public:int maxProfit(vector<int>& prices, int fee) {//[i][0]-不持有 [i][1]-持有int mprices.size();vector<vector<int>> dp(m,vector<int>(2));dp[0][0]0; //初始状态dp[0][1]-prices[0];for(int i1;i<m;i){dp[i]…...
【代码实践】HAT代码Window平台下运行实践记录
HAT是CVPR2023上的自然图像超分辨率重建论文《activating More Pixels in Image Super-Resolution Transformer》所提出的模型。本文旨在记录在Window系统下运行该官方代码(https://github.com/XPixelGroup/HAT)的过程,中间会遇到一些问题&am…...
机器学习-Pytorch基础
Numpy和Pytorch可以相互转换,前者CPU上,后者GPU上,都是对矩阵进行运算,Pytorch的基本单位是张量。torch 可以初始化全为0、全为1、符合正态分布的矩阵确定性初始化 torch.tensor()torch.arrange()torch.linspace()torch.logspace…...
金九银十,刷完这个笔记,17K不能再少了....
大家好,最近有不少小伙伴在后台留言,得准备面试了,又不知道从何下手!为了帮大家节约时间,特意准备了一份面试相关的资料,内容非常的全面,真的可以好好补一补,希望大家在都能拿到理想…...
精确到区县级街道乡镇行政边界geojson格式矢量数据的获取拼接实现Echarts数据可视化大屏地理坐标信息地图的解决方案
在Echarts制作地理信息坐标地图时,最麻烦的就是街道乡镇级别的行政geojson的获取, 文件大小 788M 文件格式 .json格式,由于是大文件数据,无法直接使用记事本或者IDE编辑器打开,推荐Dadroit Viewer(国外…...
【Python 千题 —— 基础篇】多行输出
题目描述 下面是一道关于输入输出的基础题。⭐⭐⭐ 题目描述 编写一个Python程序,将字符串 Hello World! 存储在变量 str1 中,将字符串 Hello Python! 存储在变量 str2 中,然后使用 print 语句分别将它们在不同行打印出来。 输入描述 无…...
AdaBoost(上):数据分析 | 数据挖掘 | 十大算法之一
⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ 🐴作者:秋无之地 🐴简介:CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作,主要擅长领域有:爬虫、后端、大数据…...
Py之pygraphviz:pygraphviz的简介、安装、使用方法之详细攻略
Py之pygraphviz:pygraphviz的简介、安装、使用方法之详细攻略 目录 pygraphviz的简介 pygraphviz的安装 Graphviz:可视化工具Graphviz的简介、安装、使用方法、经典案例之详细攻略 pygraphviz的使用方法 1、基础用法 2、进阶案例 Algorithm&#…...
acwing算法基础之基础算法--前缀和算法
目录 1 知识点2 模板 1 知识点 前缀后下标尽量从1开始,当然不从1开始也是ok的。 a 1 , a 2 , a 3 , . . . , a n a_1,a_2,a_3,...,a_n a1,a2,a3,...,an S 1 , S 2 , S 3 , . . . S n S_1,S_2,S_3,...S_n S1,S2,S3,...Sn S i S_i Si࿱…...
华为云云耀云服务器L实例评测|Ubuntu 22.04部署edusoho-ct企培版教程 | 支持华为云视频点播对接CDN加速
华为云云耀云服务器L实例评测|Ubuntu 22.04部署edusoho企培版教程 1、选择购买 华为云耀云服务器L实例 简单上云第一步 2、选择你要安装的操作系统,例如 Ubuntu 22.04 server 64bit 3、然后支付订单就行了 4、华为云云耀云服务器L实例创建好之后&#x…...
土木硕设计院在职转码上岸
一、个人介绍 双非土木硕,98年,目前在北京,职位为前端开发工程师,设计院在职期间自学转码上岸🌿 二、背景 本人于19年开始土木研究生生涯,研二期间去地产实习近半年(碧桂园和世茂,这两家的地产…...
js查询月份开始和结束日期
js查询月份开始和结束日期 月份开始和结束 月份开始和结束 整体不是很复杂,使用new Date()方法自带获取最后一天的时间 new Date(a,b,c),传递参数 参数a:是要获取的年份 参数b:是要获取的月份 参数c:是要获取的日期 传递日期为…...
mybatis开发部分核心代码
pom.xml<?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 ht…...
自然资源部网站绿色矿山建设/站长统计免费下载
目前,作为深度学习的代表算法之一,卷积神经网络(Convolutional Neural Networks,CNN)在计算机视觉、分类等领域上,都取得了当前最好的效果。后来,基于深度神经网络和搜索树的智能机器人“AlphaG…...
怎么知道公司网站是哪个公司做的/软文营销案例200字
1、math/rand 随机数从资源生成。包水平的函数都使用的默认的公共资源。 该资源会在程序每次运行时都产生确定的序列。如果需要每次运行产生不同的序列,应使用Seed函数进行初始化。默认资源可以安全的用于多go程并发。 关于种子seed 程序启动的时候,种…...
做网站哪/爱站网备案查询
在游标循环当中给 output 变量赋值报 指定的转换无效 错误必须在存储过程最后再给 output 变量赋值转载于:https://www.cnblogs.com/hjtdlx/p/3761001.html...
电镀加工技术支持 东莞网站建设/在线观看的seo综合查询
2013-08-02 14:06 9826人阅读 评论(2) 收藏 举报 版权声明:本文为博主原创文章,未经博主允许不得转载。 总结一下java 时间戳和PHP时间戳 的转换问题: 由于精度不同,导致长度不一致,直接转换错误。 JAVA时间戳长度…...
用个人电脑做网站的步骤/搜索引擎优化seo信息
在研究零知识证明时候,发现了18年底一个叫超零币(SERO)的新兴电子币,据说使用的super-ZK速度比Zcash的zk-SNARKs快20倍,对此非常感兴趣,找到官方博客,进行环境搭建。 官方博客:http…...
做网站用哪里的服务器比较好/百度推广投诉中心
【单选题】与文件系统阶段相比,关系数据库技术的数据管理方式具有许多特点,但不包括【填空题】输入矩阵 A[3,2,5;8,-5,7;-1,4,9] ,使用全下标方式用 A(2,2) 取出元素 ,使用单下标方式用 取出元素 “-5 ” 。【单选题】如果要回滚一个事务,则要使用( )语句【单选题】SELECT语句的…...