Python新手在作用域方面经常容易碰到的问题


Posted in Python onApril 03, 2015

通常,当我们定义了一个全局变量(好吧,我这样说是因为讲解的需要——全局变量是不好的),我们用一个函数访问它们是能被Python理解的:
 

bar = 42
def foo():
  print bar

在这里,我们在foo函数里使用了全局变量bar,然后它也如预想的能够正常运行:
 

>>> foo()
42

这样做很酷。通常,我们在使用了这个特性之后就想在所有的代码里用上它。如果像以下的例子中使用的话还是能够正常运行的:
 

bar = [42]
def foo():
  bar.append(0)
foo()
 
>>> print bar
[42, 0]

但是,如果我们把bar变一下呢:
 

>>> bar = 42
... def foo():
...   bar = 0
... foo()
... print bar
42

我们可以看到foo函数运行的好好的并且没有抛出异常,但是当我们打印bar的值的时候会发现它的值仍然是42。造成这种情况的原因就是 bar=0 这行代码,它没有改变全局变量bar的值,而是创建了一个名字也叫bar的局部变量并且它的值为0。这是个很难发现的bug,这会让没有真正理解Python作用域的新手非常痛苦。为了理解Python是如何处理局部变量和全局变量的,我们来看一种更少见的,但是可能会更让人困惑的错误,我们在打印bar的值后定义一个叫bar这个局部变量:
 

bar = 42
def foo():
  print bar
  bar = 0

这样写应该是不会出错的,不是吗?我们在打印了值之后定义了相同名称的变量,所以这应该是不会影响的(Python毕竟是一种解释型语言),真的是这样吗?

 

出错了

这怎么可能呢?好吧,这里有两处错误。第一点就是关于Python的,作为一种解释型语言(非常酷,我们都同意这一点),是一行一行地执行的。而事实上,Python是一个声明一个声明执行的。为了让你对我想表达的意思有点感觉,赶紧打开你最爱的shell,然后输入以下代码:
 

def foo():

按回车键。正如你看到的,shell里面并没有打出任何输出而是等着让你继续函数的定义。Shell里会一直这样直到你停止定义函数。这是因为定义函数是一个声明。好吧,这是一个混合的声明,里面包含了一些其他的声明,但它仍然是一个声明。直到函数被调用,不然这个函数里的内容是不会执行的。真正执行的是一个function类型的对象被创建出来了。

这引导我们来关注第二点。再强调一下,Python的动态性和解释型的特性让我们相信当 print bar 这行被执行的时候,Python会在首先在局部作用域里寻找叫bar的变量然后再去寻找全局作用域里的。但实际上发生的是局部作用域不是完全动态的。当def 这个声明执行的时候,Python会静态地从这个函数的局部作用域里获取信息。当来到 bar=0 这行的时候(不是执行到这行代码,而是当Python解释器读到这行代码的时候),它会把'bar'这个变量加入到foo函数的局部变量列表里。当foo函数执行并且Python准备执行print bar这行的时候,它就会在局部的作用域里寻找这个变量,由于这个过程是静态的,Python知道这个变量还没有被赋值,这个变量没有值,所以抛出了异常。

你可能会问:为什么不能在声明函数的时候抛出这个异常呢?Python可以知道预先知道bar这个变量在赋值前被引用了。这个问题的答案就是Python无法知道这个局部变量bar是否被赋值了。看看下面的例子:
 

bar = 42
def foo(baz):
  if baz > 0:
    print bar
  bar = 0

Python在动态和静态之间玩了一个微妙的游戏。它唯一知道的事情就是bar是被赋值了,但它不知道在赋值前被引用这个异常是否存在直到它真的发生。好吧,老实说,它根本就不知道这个变量是否被赋值!
 

bar = 42
def foo():
  print bar
  if False:
    bar = 0
 
>>> foo()
Traceback (most recent call last):
 File "<pyshell#17>", line 1, in <module>
  foo()
 File "<pyshell#16>", line 3, in foo
  print bar
UnboundLocalError: local variable 'bar' referenced before assignment

看到上面的代码里面,虽然我们作为一种智能生物能够很清楚的知道不会给bar赋值。Python无视了那个事实而是仍然声明了bar这个局部变量。

关于这个问题我已经说了够长了。我们需要的是解决方案,我会在这里给出两个解决方法。
 

>>> bar = 42
... def foo():
...   global bar
...   print bar
...   bar = 0
...
... foo()
42
>>> bar
0

第一就是使用global关键字。这是不言自明的。这会让Python知道bar是一个全局变量而不是局部变量。

第二个方法,也是更推荐使用的,就是不要使用全局变量。在我的大量Python开发工作中从来没有用到global这个关键字。能知道怎么用它就行了,但最终还是要尽量避免使用它。如果你想保存在代码里至始至终用到的值的时候,把它定义为一个类的属性。用这种方法的话就完全不需要用global了,当你要用这个值的时候,通过类的属性来访问就可以了:
 

>>> class Baz(object):
...   bar = 42
...
... def foo():
...   print Baz.bar # global
...   bar = 0 # local
...   Baz.bar = 8 # global
...   print bar
...
... foo()
... print Baz.bar
42
0
8
Python 相关文章推荐
python实现多线程暴力破解登陆路由器功能代码分享
Jan 04 Python
python+requests+unittest API接口测试实例(详解)
Jun 10 Python
python email smtplib模块发送邮件代码实例
Apr 26 Python
Python使用Dijkstra算法实现求解图中最短路径距离问题详解
May 16 Python
NumPy 数学函数及代数运算的实现代码
Jul 18 Python
Django 数据库同步操作技巧详解
Jul 19 Python
Python实现隐马尔可夫模型的前向后向算法的示例代码
Dec 31 Python
python+tifffile之tiff文件读写方式
Jan 13 Python
使用Python+selenium实现第一个自动化测试脚本
Mar 17 Python
python安装和pycharm环境搭建设置方法
May 27 Python
浅析Python 抽象工厂模式的优缺点
Jul 13 Python
C++和python实现阿姆斯特朗数字查找实例代码
Dec 07 Python
Python中设置变量作为默认值时容易遇到的错误
Apr 03 #Python
用Python编写一个简单的Lisp解释器的教程
Apr 03 #Python
举例讲解Python中is和id的用法
Apr 03 #Python
详解Python2.x中对Unicode编码的使用
Apr 03 #Python
对于Python中线程问题的简单讲解
Apr 03 #Python
python BeautifulSoup设置页面编码的方法
Apr 03 #Python
用Python编写一个简单的FUSE文件系统的教程
Apr 02 #Python
You might like
《星际争霸重制版》兵种对比图鉴
2020/03/02 星际争霸
CentOS 6.2使用yum安装LAMP以及phpMyadmin详解
2013/06/17 PHP
php获取apk包信息的方法
2014/08/15 PHP
WordPress中自定义后台管理界面配色方案的小技巧
2015/12/29 PHP
PHP登录验证功能示例【用户名、密码、验证码、数据库、已登陆验证、自动登录和注销登录等】
2019/02/25 PHP
WordPress JQuery处理沙发头像
2009/06/22 Javascript
js URL参数的拼接方法比较
2012/02/15 Javascript
js导航菜单(自写)简单大方
2013/03/28 Javascript
JS右下角广告窗口代码(可收缩、展开及关闭)
2015/09/04 Javascript
js和jQuery设置Opacity半透明 兼容IE6
2016/05/24 Javascript
AngularJs定制样式插入到ueditor中的问题小结
2016/08/01 Javascript
利用jQuery的动画函数animate实现豌豆发射效果
2016/08/28 Javascript
Angular2学习笔记——详解路由器模型(Router)
2016/12/02 Javascript
jquery 标签 隔若干行加空白或者加虚线的方法
2016/12/07 Javascript
微信小程序如何刷新当前界面的实现方法
2019/06/07 Javascript
elementui之el-tebs浏览器卡死的问题和使用报错未注册问题
2019/07/06 Javascript
[20:39]DOTA2-DPC中国联赛 正赛开幕式 1月18日
2021/03/11 DOTA
python操作gmail实例
2015/01/14 Python
Python使用pylab库实现绘制直方图功能示例
2018/06/01 Python
python+influxdb+shell编写区域网络状况表
2018/07/27 Python
python计算n的阶乘的方法代码
2019/10/25 Python
python使用html2text库实现从HTML转markdown的方法详解
2020/02/21 Python
Python DataFrame使用drop_duplicates()函数去重(保留重复值,取重复值)
2020/07/20 Python
CSS3+HTML5+JS 实现一个块的收缩与展开动画效果
2020/11/17 HTML / CSS
理肤泉加拿大官网:La Roche-Posay加拿大
2018/07/06 全球购物
怎样创建、运行java程序
2014/08/01 面试题
职业女性的职业规划
2014/03/04 职场文书
庆六一文艺汇演活动方案
2014/08/26 职场文书
村干部群众路线整改措施思想汇报
2014/10/12 职场文书
婚前协议书标准版
2014/10/19 职场文书
2014年银行柜员工作总结
2014/11/12 职场文书
2014年团支部工作总结
2014/11/17 职场文书
表扬稿格式范文
2015/01/16 职场文书
工作保证书
2015/01/17 职场文书
SQL Server连接查询的实用教程
2021/04/07 SQL Server
Pytorch DataLoader shuffle验证方式
2021/06/02 Python