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

?

1

2

3

bar= 42

def foo():

print bar

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

?

1

2

>>> foo()

42

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

?

1

2

3

4

5

6

7

bar= [42]

def foo():

bar.append(0)

foo()

>>>print bar

[42,0]

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

?

1

2

3

4

5

6

>>> bar= 42

...def foo():

... bar= 0

... foo()

...print bar

42

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

?

1

2

3

4

bar= 42

def foo():

print bar

bar= 0

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

出错了

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

?

1

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是否被赋值了。看看下面的例子:

?

1

2

3

4

5

bar= 42

def foo(baz):

if baz >0:

print bar

bar= 0

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

bar= 42

def foo():

print bar

if False:

bar= 0

>>> foo()

Traceback (most recent call last):

File "", line1,in

foo()

File "", line3,in foo

print bar

UnboundLocalError: local variable'bar' referenced before assignment

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

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

?

1

2

3

4

5

6

7

8

9

10

>>> bar= 42

...def foo():

...global bar

...print bar

... bar= 0

...

... foo()

42

>>> bar

0

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

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

>>>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新手在作用域方面经常容易碰到的问题相关推荐

  1. python自动化面试常问问题_Python自动化测试常见面试题及解析答案

    一._ init _ _ 和 _ _ new _ _是什么? 1.1.概述 在python的面向对象中,利用new方法创建对象,然后把这个创建的对象传递到init函数中,以第一个参数进 1.2.详细描 ...

  2. python爬虫记录时间_Python新手写爬虫全过程记录分析

    目标网站:http://bohaishibei.com/post/category/main/(一个很有趣的网站,一段话配一个图,老有意思了-)网站形式如下: 目标:把大的目标分为几个小的目标.因为第 ...

  3. python 命名空间报错_python命名空间与作用域

    命名空间不共享名称. 在命名空间中的名称能将任何python对象作为值,在不同的命名空间中相同的名称可以与不同的对象相关联.但是,如果存在名称解析协议,则多个命名空间可以一起工作来解析名称.也就是说, ...

  4. python如何问问题_Python简单问答程序如何解决问题循环

    Python学习者(2.7.5版).在 目前我正在开发一个简单的测试脚本,允许用户重新回答问题,并限制用户错误回答问题的机会.在 因此设置了5个总限制,并显示一条消息(例如"END!&quo ...

  5. python实训计划_Python新手自学计划

    作为目前最火也是最实用的编程语言,Python不仅是新手入门程序界的首选,也逐渐成为了从大厂到小厂,招牌需求list的必要一条.当然,自学Python这件事情,估计好多人和我一样,已经下了一百次决心, ...

  6. python定义一个变量_Python新手上车3:Python第一个程序和变量

    第一个程序 上节内容我们提到了如何安装Python 以及安装配置VSCode,这节内容我们继续介绍第一个程序的建立: 1.在VS的左上角点击建立一个新的程序,命名为 python_0301_code. ...

  7. python结构化数据_python新手入门必备—— 使用json保存结构化数据

    [摘要]使用 json 保存结构化数据时,字符串可以很轻松地写入文件并从文件中读取出来.数字可能会费点劲,因为 read() 方法只能返回字符串,这些字符串必须传递给类似 int() 的函数,它会接受 ...

  8. python安装界面翻译_Python新手教程:40行python代码写一个桌面翻译器

    这是我做出来的粗略版本,后面的UI设计就看大家的艺术细胞了 Python资源共享群:484031800 我们进行制作软件所需要的模板库,首先要进行引用. # json requests thinter ...

  9. python教程龟叔_Python新手入门

    Python简介 Python是著名的"龟叔"Guido van Rossum在1989年圣诞节期间,为了打发无聊的圣诞节而编写的一个编程语言. Python提供了非常完善的基础代 ...

最新文章

  1. 2.安装 CLI和CLI的工作原理
  2. boost::hana::is_disjoint用法的测试程序
  3. C++ gnome sort 侏儒排序的实现算法(附完整源码)
  4. matlab实现获取tof,一种结合TOF技术和双目视觉的深度信息获取装置及其方法与流程...
  5. 在DataGridView控件中一次显示出多个圆饼图的原始程序代码
  6. 莒南机器人_莒南五小学子 在2020年世界FTF青少年无人机大赛中 荣获佳绩
  7. 3年后准确率仍达97%:利用谷歌语音转文本 API 绕过reCAPTCHA
  8. lstm原文_对时间序列分类的LSTM全卷积网络的见解
  9. 巧用DOS命令上传文件
  10. 阅读go语言程序设计
  11. vue loading组件
  12. Matlab程序接口应用总结
  13. Python代码: 把几个PDF文件拼接为一个 Merge PDF files
  14. 【OpenCV + Python】时域和频域傅里叶变换
  15. 2048游戏的核心运算
  16. 2019-6-2 第2套试卷中的生词(02)
  17. Minor GC、Young GC、Old GC、Major GC、Mixed GC、Full GC都是什么?
  18. php在线留言,PHP在线提交留言直接发到邮箱
  19. MATLAB轨迹规划 发给ROS中机器人实现仿真运动
  20. Android 超好用的万能播放器——ijkplayer

热门文章

  1. docker启动失败Failed to start Docker Application Container Engine.(centos7)
  2. 解决springboot工程创建是连接失败问题
  3. 服务器环控信息,服务管理监控平台
  4. java解析pom.xml_从pom.xml java获取变量
  5. 神经网络 测试集loss不下降_代码实践 | 全连接神经网络回归---房价预测
  6. OpenCV案例(三): 玉米颗粒计数
  7. 解决XP中CPU占用率过高问题, 关了445这个端口(ZT)
  8. 未初始化的类,通过指针调用其非静态函数
  9. Hao Yin Jian 寒假第一周
  10. Assets和Raw区别