前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >变量的作用域

变量的作用域

作者头像
烟草的香味
发布2021-01-20 15:26:54
9420
发布2021-01-20 15:26:54
举报
文章被收录于专栏:烟草的香味

起因

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

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

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

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

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

代码语言:javascript
复制
x = 2

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

re_f()

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

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

回顾历史

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

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

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

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

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

长变量名

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

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

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

变量回写

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

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

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

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

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

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

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

举个简单的例子:

代码语言:javascript
复制
$i = 1;

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

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

动态作用域

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

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

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

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

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

静态作用域

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

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

现象

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

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

代码语言:javascript
复制
x = 2

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

re_f()

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

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

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

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

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-01-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 烟草的香味 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 起因
  • 回顾历史
    • 长变量名
      • 变量回写
        • 动态作用域
          • 静态作用域
          • 现象
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档