转发自:http://blog.jobbole.com/43826/python
在以前几个月里,我教一些不了解Python的孩子来慢慢熟悉这门语言。渐渐地,我发现了一些几乎全部Python初学者都会犯的错误,因此我决定跟来跟你们分享个人建议。这个系列的每一个部分都会关注不一样的常见错误,描述如何产生这种错误的,而且提供解决的方法。本文是第二部分。shell
做用域app
在这篇文章里,咱们来关注做用域在Python被误用的地方。一般,当咱们定义了一个全局变量(好吧,我这样说是由于讲解的须要——全局变量是很差的),咱们用一个函数访问它们是能被Python理解的:函数
1
2
3
|
bar
=
42
def
foo():
print
bar
|
在这里,咱们在foo函数里使用了全局变量bar,而后它也如预想的可以正常运行:spa
1
2
|
>>> foo()
42
|
这样作很酷。一般,咱们在使用了这个特性以后就想在全部的代码里用上它。若是像如下的例子中使用的话仍是可以正常运行的:code
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这个局部变量:blog
1
2
3
4
|
bar
=
42
def
foo():
print
bar
bar
=
0
|
这样写应该是不会出错的,不是吗?咱们在打印了值以后定义了相同名称的变量,因此这应该是不会影响的(Python毕竟是一种解释型语言),真的是这样吗?游戏
出错了ci
这怎么可能呢?好吧,这里有两处错误。第一点就是关于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
"<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这个局部变量。
关于这个问题我已经说了够长了。咱们须要的是解决方案,我会在这里给出两个解决方法。
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
|