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

通常,当我们定义了一个全局变量(好吧,我这样说是因为讲解的需要——全局变量是不好的),我们用一个函数访问它们是能被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
(0)

相关推荐

  • Python运行的17个时新手常见错误小结

    1)忘记在 if , elif , else , for , while , class ,def 声明末尾添加 :(导致 "SyntaxError :invalid syntax") 该错误将发生在类似如下代码中: 复制代码 代码如下: if spam == 42 print('Hello!') 2)使用 = 而不是 ==(导致"SyntaxError: invalid syntax") = 是赋值操作符而 == 是等于比较操作.该错误发生在如下代码中: 复制代码

  • Python 功能和特点(新手必学)

    Python是一门简单而文字简约的语言.阅读好的Python程序感觉就像阅读英语,尽管是非常严格的英语.Python的这种伪代码特性是其最大强项之一,它可让你专注于解决问题的办法而不是语言本身. 在使用Python多年以后,我偶然发现了一些我们过去不知道的功能和特性.一些可以说是非常有用,但却没有充分利用.考虑到这一点,我编辑了一些的你应该了解的Pyghon功能特色. 带任意数量参数的函数 你可能已经知道了Python允许你定义可选参数.但还有一个方法,可以定义函数任意数量的参数. 首先,看下面

  • 深入理解 Python 中的多线程 新手必看

    示例1 我们将要请求五个不同的url: 单线程 import time import urllib2 defget_responses(): urls=[ 'http://www.baidu.com', 'http://www.amazon.com', 'http://www.ebay.com', 'http://www.alibaba.com', 'http://www.jb51.net' ] start=time.time() forurlinurls: printurl resp=urll

  • Python新手们容易犯的几个错误总结

    前言 这篇文章主要给大家总结了关于学习Python的新手们容易犯的几个错误,一共四个易犯错误,下面来看看详细的介绍吧. 一.i+=1 不等于++i 初学者对Python语言不是特别了解的话,又正好有c++,java的语言背景,很容易把++i和i+=1弄混 先来看一个小例子: i=0 mylist=[1,2,3,4,5,6] while i <len(mylist): print(mylist[i]) ++i 这段代码会想当然的认为,没有啥问题啊,一个循环输出,i不断的+1,蛮对的呀.其实不是的,

  • 一篇文章入门Python生态系统(Python新手入门指导)

    译者按:原文写于2011年末,虽然文中关于Python 3的一些说法可以说已经不成立了,但是作为一篇面向从其他语言转型到Python的程序员来说,本文对Python的生态系统还是做了较为全面的介绍.文中提到了一些第三方库,但是Python社区中强大的第三方库并不止这些,欢迎各位Pytonistas补充. •原文链接:http://mirnazim.org/writings/python-ecosystem-introduction/ •译文链接:http://codingpy.com/artic

  • Python完全新手教程

    Python入门教程FROM:http://www.cnblogs.com/taowen/articles/11239.aspx作者:taowen, billrice Lesson 1 准备好学习Python的环境 下载的地址是: www.python.org linux版本的我就不说了,因为如果你能够使用linux并安装好说明你可以一切自己搞定的. 运行环境可以是linux或者是windows: 1.linux redhat的linux安装上去之后一定会有python的(必须的组件),在命令行

  • python新手经常遇到的17个错误分析

    1)忘记在 if , elif , else , for , while , class ,def 声明末尾添加 :(导致 "SyntaxError :invalid syntax") 该错误将发生在类似如下代码中: if spam== 42 print('Hello!') 2) 使用 = 而不是 ==(导致"SyntaxError: invalid syntax") = 是赋值操作符而 == 是等于比较操作.该错误发生在如下代码中: if spam= 42: pr

  • 对Python新手编程过程中如何规避一些常见问题的建议

    这篇文章收集了我在Python新手开发者写的代码中所见到的不规范但偶尔又很微妙的问题.本文的目的是为了帮助那些新手开发者渡过写出丑陋的Python代码的阶段.为了照顾目标读者,本文做了一些简化(例如:在讨论迭代器的时候忽略了生成器和强大的迭代工具itertools). 对于那些新手开发者,总有一些使用反模式的理由,我已经尝试在可能的地方给出了这些理由.但通常这些反模式会造成代码缺乏可读性.更容易出bug且不符合Python的代码风格.如果你想要寻找更多的相关介绍资料,我极力推荐The Pytho

  • 新手该如何学python怎么学好python?

    根据本人的学习经验,我总结了以下十点和大家分享: 1)学好python的第一步,就是马上到www.python.org网站上下载一个python版本.我建议初学者,不要下载具有IDE功能的集成开发环境,比如Eclipse插件等. 2)下载完毕后,就可以开始学习了.学习过程中,我建议可以下载一些python的学习文档,比如<dive into python>,<OReilly - Learning Python>等等.通过学习语法,掌握python中的关键字语法,函数语法,数学表达式

  • Python新手实现2048小游戏

    接触 Python 不久,看到很多人写2048,自己也捣鼓了一个,主要是熟悉Python语法. 程序使用Python3 写的,代码150行左右,基于控制台,方向键使用输入字符模拟. 演示图片 2048.py # -*- coding:UTF-8 -*- #! /usr/bin/python3 import random v = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] def display(v, score): '''显示

随机推荐