变量的作用域

起因

最近闲来无事, 在 Python 官网上看到了2.0版本, 是2001年的.

image-20210116142522829

打算装起来体验一下最初发布的版本, 但是发现只有 Windows 版本, 所以我就装了个 Windows10的虚拟机, 就在我打算安装的时候, 发现:

image-20210116142802711

这激起了我的好胜欲, 于是我就依次安装了Windows 8, Windows 7, Windows XP, 功夫不负有心人, 终于在XP系统上装上了. (现在的很多网站, 在XP系统的 ie 上已经打不开了. )

想必大家没怎么见过Python 2.0的安装过程 吧, 在此截图留念:








中间各种试用, 在此按下不表, 来看一下出问题的代码:

x = 2def re_f():x = 3def tmp_f():print xtmp_f()re_f()

这段代码的输出结果是什么? 按常理来说, 应该是3, 没错吧. 但是, 你看:

image-20210116144427156

??? 什么鬼? 为什么读到了全局变量? 我还特地有到Python 3.0的环境中跑了一遍, 发现结果确实是3啊. 不懂就要问, 于是我开始搜寻各种资料, 发现这设计到了变量的作用域.

回顾历史

要想理解这个现象, 就得把时间线往回拉, 拉到什么时候呢? 就从汇编说起.

在早期的汇编中, 对一个变量定义后, 就作为全局变量作用于整个程序. 在编译之后, 将所有该变量名替换为正确的地址, 相当于维护了一个变量名到地址的映射表.

当然, 这并没有什么问题, 但是随着时间推移, 程序的规模越来越大, 问题就出现了.

你定义了一个变量 x=2, 调用了一个系统函数之后, 回来发现x变成9了. 因为系统函数中也存在变量x, 这很明显会引发各种各样的问题, 开发难度大幅度提升.

如何解决这个问题呢? 出现问题的根源就是, 定义的变量都是全局变量, 每个修改其变量的人, 都会影响所有使用者. 接下来有了各种解决办法:

长变量名

既然出问题的原因是使用了同名变量, 那我让所有变量的名字都不一样就可以嘛.

在函数sort中的所有变量, 都加上_sort后缀, 比如变量i, 就定义为i_sort, 但无法避免另外一个sort函数, 那就在后缀再拼上一个文件名? 但如果文件名也一样呢? 毕竟很多时候, 你需要调用各种现有的库, 你无法保证没有冲突.

很显然, 这并不能解决本质问题.

变量回写

既然同名这个方向走不通了, 那就往全局方向使劲吧. 如果能让变量只在当前函数起作用, 而不会被其他人随意修改, 不就能够解决这个问题了么?

说起来容易, 如何实现呢? 如果说, 我在函数退出的时候, 把变量再改回我进来时候的样子, 不就能假装什么都没有发生吗? 比如这样:

function test(){// 这里用到了变量 i, 那就先把原来的值记下来$old_i = $i;// 然后就可以随意对变量 i 修改了// 返回时将变量改回去$i = $old_i;return;
}

但是, 这种处理方法有如下问题 :

问题1: 若old_i变量也是个全局变量怎么办

对于这个问题还是很好处理的, 编译器是有全局变量的对照表的, 随便找一个不存在的变量还是很容易的, 这个赋值的操作直接交给编译器来处理就好.

问题2: 上层函数的修改会影响下层函数

举个简单的例子:

$i = 1;function fun_1(){$old_i = $i;$i = 2;fun_2();$i = $old_i;return;
}

这里有一个全局变量i, 在fun_2中读到的变量i值是多少? 是2. 函数fun_1本无意修改i的值, 但其修改还是影响了所有下层函数. 当然, 有时确实需要读取上层函数的修改, 但是, 也有很多情况是要读到其原始值的.

动态作用域

无法读取到全局变量的原因, 是变量的值在上层函数中已经被修改了, 其原本的值已经不存在了. 如何实现真正的局部变量, 保证不会对全局变量造成污染呢? 很简单, 只要函数的变量与全局变量, 实际指向的地址不同就可以了. 如何实现呢?

函数使用一张自己的变量名对照表, 就可以了. 大概就长这样:

image-20210116160120405

这样, 函数使用的变量就是真正的局部变量了. 当函数fun_1退出的时候, 会将对应的对照表销毁.

这个时候, 函数fun_2读取变量$i的时候, 会按照对照表的创建顺序, 在fun_2变量对照表, fun_1变量对照表, 全局变量对照表 依次查找, 看哪一个先找到.

哎, 这不就是闭包么. 动态作用域读取变量的结果, 其实与上方的回写变量的方式差不多, 不同的是, 动态作用域保留了全局变量原始的值. 既然原始值留下来了, 那自然就要能够读到, 否则留他何用, 读取的方式就是下面的静态作用域了.

静态作用域

静态作用域也是通过变量的对照表来实现, 与动态作用域不同的是, 每个函数能看到的变量对照表只有自己的和全局的, 上面的函数调用, 换成静态作用域大概如下:

image-20210116160536236

这样就能让函数绕过上层, 直接访问全局变量了.

现象

了解了变量作用域相关内容, 也就能够解释最开始遇到的现象了.

再来回看一下最开始的问题, 为什么在Python 2.0中, 闭包读取到的变量是全局变量呢? 很明显, 其使用了静态作用域导致的. 那么在2.0中如何解决这个问题呢? 传参, 修改之后的代码:

x = 2def re_f():x = 3def tmp_f(x):print xtmp_f(x)re_f()

再次执行, 结果与预期一致, 是3

而到了Python 2.1.3就已经改为动态作用域了. (也不知道为什么2.1比2.2还要晚一年发布)

在函数中如果想修改外部变量, 需要对变量进行声明, 若不声明则创建本地变量. 在 Python 中有两个关键字对变量进行声明:

  • global: 声明全局变量, 既通过静态作用域的方式查找变量
  • nolocal: 通过动态作用域的方式查找变量

当然, Python中通过上面关键字标识的变量修改, 会直接修改外层变量的值, 个人还是推荐以返回值的形式处理.

我是真的闲, 为了装Python2.0我就搞了半天, 查作用域又查了三四个小时.

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

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

相关文章

PHP8的注解

PHP8.0增加了注解的支持, 虽然 PHP的注解没用过, 但是咱用过JAVA的注解呀. 注解这玩意怎么用? 简单说就下面几步: 定义注解类使用注解提取注解 到了PHP中, 也基本上换汤不换药. 使用 定义注解类 #[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_FUNCTION)] cl…

目的论浅谈

一些心理学主张, 我们的现在乃至未来, 都是由过去所经历的事情所决定的, 这种思想被称为 “原因论”, 同时也符合佛家的思想. 而相对的另一种解释就是 “目的论”. 举个例子, 如果你在饭店吃饭, 被服务员不小心把汤打翻了, 泼了你一身. 此时, 你愤怒不已, 站起身来破口大骂, 此…

HBase 数据存储结构

在HBase中, 从逻辑上来讲数据大概就长这样: 单从图中的逻辑模型来看, HBase 和 MySQL 的区别就是: 将不同的列归属与同一个列族下支持多版本数据 这看着感觉也没有那么太大的区别呀, 它解决了 MySQL 的那些问题呢? 每一个新事物的出现, 都是为了解决原本存在的问题. 对写入…

HBase 文件合并

HBase在存储时, 使用了LSM树来进行数据存储, 会定期将文件进行合并, 以提升数据的查询效率, LSM树都是这么处理的. 那么到这里就有一个问题了, HBase在进行文件合并的时候, 势必会占用大量 IO, 难道不会对正常的业务产生影响么? 抱着这个疑问, 我去找了找HBase文件合并的方式.…

spark计算操作整理

spark 的计算流程大概如图: 其中, 通过多次处理, 生成多个中间数据, 最后对结果进行操作获得数据. 本文不涉及任何原理, 仅总结spark在处理的时候支持的所有操作, 方便后面使用的时候, 可以参照本文进行数据的处理. 以下函数整理, 基与Python中RDD对象. 数据的转换操作 数据…

软件工程模型

你在工作中, 软件的开发流程是怎样的? 你是否想过, 除了你当前使用的流程, 还存在其他怎样的流程? 现在的流程有哪些问题, 又能够如何解决? 别说, 前辈们已经给出了一些项目流程的模型, 既软件工程. 可以简单了解一下, 带动一下我这生了锈的脑子. 在很久以前, 一个软件的从…

关于对接需求的思考

产品说想要一个登录注册的功能, 你一想, 好说, 不就是用户名密码嘛, 然后开发完成 产品看到成果后: 我要的是手机验证码登录结果写好的功能基本废了 产品又想要一个登录注册的功能, 这回你学乖了, 确认了一下, 是手机验证码登录, 没问题, 然后开发完成 产品拿到成果: 怎么没有验…

路径.git下的文件

用了这么久的git, 可以毫不谦虚地说对git是一无所知. 每天用来用去的就是commit, add, merge 等几个有限的命令, 这不符合我这刨根问底的性格啊. 不行, 得研究研究, 从哪里下手呢? 别的咱先不说, 所有 git 项目都有这么一个文件夹.git, 不如就从它入手 ? 那咱就看看这个文件夹…

git 操作二进制文件

平常用git进行项目管理已经稀松平常了, 今天咱来点不一样的. 平常管理的都是普通的文本文件, 如果是二进制文件, git能够处理么? 比如word文档. 测试一下. 新建一个项目, 在其中创建test1.docx, test2.txt两个空文件并提交. 之后编辑文件并添加标题, git diff看一下效果: 效…

Go 常量定义

定义常量在各个语言中都是不可或缺的语法了. 而有些语言在常量的基础上, 增加了枚举类型, 比如C. enum Weekday {SUNDAY,MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY };上面的枚举, 对应的值依次为0到6. 而在Go中, 是没有提供枚举类型的. 如果实现上面相同的功能, 难…

搭建个人博客

一直都想着搞一个自己的个人博客, 拖着这么久, 最近终于开始动手了. 故留下一篇完整的记录, 若你也刚好有相同的需要, 那这篇文章应该恰好能够帮助到你. 准备 云服务器个人域名 如果没有云服务器, 将应用跑在自己的电脑上, 通过内网穿透大概也能达到效果, 但是个人电脑实在没…

nginx 配置文件的匹配规则

引出 之前在对php-fpm 进行nginx代理时, 为了对后台限定 IP 访问, 添加了如下配置: location ^~ /admin {allow 127.0.0.1;deny all; }结果呢? 所有admin路径下的php文件, 全都没有解析, 变成文件下载了. 当时我不知道是什么问题, 不过将这段配置去掉之后, 问题就消失了. 所…

分库后如何分页

前言 在实际应用中, 为了降低单表的数据量, 会对较大的表进行水平切分, 将单表的数据切分到多表多库中. 既然要切分, 就要有一个切分的依据, 比如说按照 ID 取模等. 那么多张表联合分页是如何做到的呢? 如果分表的依据是字段 A, 但是需要根据字段 B 进行分页查询, 针对这种…

计算机是如何进行时间同步的

WHY 在网络世界中, 各个计算机之间要想协同工作, 时间同步是一个十分重要的基础. 在计算机内部是有自己的时间的, 这个时间通过内部的晶体振荡器差生的固定频率, 来模拟时间流逝进行计算. 虽然频率十分稳定, 但也是有误差的, 虽然现在的工艺水平误差已经十分小了. (关于震荡的…

WordPress架构简单剖析

前言 最近在搭建自己的博客站点时, 选择了网站使用较多的WordPress, 随着慢慢的使用, 它灵活的插件和主题令我折服. 基本上任何想要实现的功能, 都可以在上面通过插件的形式进行添加. 无论是在访问前的缓存、访问后的统计、访问中的过滤、各种流程的修改等等, 几乎都能够以插件…

Gale-Shapley算法

前言 最近看了一档综艺《心动的信号》(唉, 单身久了, 开始喜欢看别人谈恋爱了) 节目中共有n男n女, 他们会在节目的最后进行表白, 如果我喜欢你, 恰好你也喜欢我, 那么便就会在一起, 自此传为一段佳话. 于是, 我就在想, 如何用算法来实现这个匹配的过程呢? 单一匹配 将信息…

阿里云定时任务并自动释放

前言 最近写了一个爬虫脚本, 脚本跑在一台北京的 ecs 上. 但奈何因某种未知力量, 需要连接代理才能访问目标网站. 本来想着自己搭代理, 但是太贵了, 就暂时搁置了. 直到我发现了这个: 阿里云香港的服务器, 一个小时才5分钱. 如果脚本直接跑在香港服务器上不就可以了咩, 按照这…

智能优化算法应用:基于金豺算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于金豺算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于金豺算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.金豺算法4.实验参数设定5.算法结果6.参考文献7.MA…

pixiv小控件

前言 最近看到一个大佬, 开源了一款博客小插件, 地址. 可以将pixiv网站的日榜放到博客侧边栏. 看上去很炫酷. 于是我也引入到了自己的博客中. 在此向大佬表示感谢. 但是在使用过程中, 经常遇到访问很慢的情况, 查看之后才发现, 大佬的服务器架设在韩国, 难怪访问比较慢, 都走…

PHP-PDO参数绑定问题

前言 今天在执行这样一段代码: $data [username > hujingnb,address > beijing, ]; $dbh new PDO("mysql:host{$host};dbname{$dbname}", $username, $password); $statement $dbh->prepare(INSERT INTO test_user (username, address) VALUES (:usern…