Python 新手常犯错误

Python 新手常犯错误(第二部分)

转发自:http://blog.jobbole.com/43826/

作用域

在这篇文章里,我们来关注作用域在Python被误用的地方。通常,当我们定义了一个全局变量(好吧,我这样说是因为讲解的需要——全局变量是不好的),我们用一个函数访问它们是能被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 "<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

时间: 2024-12-23 05:03:21

Python 新手常犯错误的相关文章

python新手常犯的17个错误

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

C#新手常犯的错误汇总

本文所述为C#新手常犯的错误,但是实际上很多有经验的程序员也经常犯这些错误,对此特别整理了一下,供大家参考.具体如下: 1.遍历List的错误 ,比如如下代码: List<String> strList =newList<String> for(int i =0; i<strList.Count; i++) { strList.RemoveAt(i); } 这段代码看上去是删除了所有元素,实际上每次调用RemoveAt方法会导致List元素索引重排,最后导致元素没有完全删除.

web前端常犯错误集锦

html部分 1.head中不加doctype的类型,会导致浏览器兼容性的问题 2. id用数字来表示 3.文件编码与meta规定的charset不一致 Mysql部分 Mysql两个常见引擎 ,区别 1.MyIsAM 我的理解是作为只读表,则使用该引擎,该引擎对查询有优化,并且改善了索引树的空间利用率:该引擎没有事务的控制:不太容易发生死锁 2.InnoDB 事务表引擎,保证事务的完整性:如果该表更新频繁,则使用该表引擎:如果数据量巨大,也应该使用该表引擎,并且有自己的缓冲池: 容易发生死锁

scanf()常犯错误

------------------------------------------------------------------------ <1> 本意:接收字符串. 写成代码:void main() { char *str; scanf("%s",str); printf("string is: %s\n",str); } 符合愿意代码:char *str=NULL; str=malloc(128*sizeof(char) ); scanf( &

【小编亲历】10个新手UI设计师常犯错误,小编已中招,你呢?

以下内容由Mockplus团队翻译整理,仅供学习交流,Mockplus是更快更简单的原型设计工具. 新手入坑,难免会做蠢事,犯错误.当初,作为UI设计新手菜鸟的小编, 也没少犯错误,走弯路.所以,小编这里为大家分享10个当初常犯的设计错误,希望能够帮助刚入行的小伙伴们尽量少走一些弯路. 接下来我们就一起看看都有哪些坑吧: 1.从未归类整理设计文件和资料 事实上,在最初开始UI设计阶段,小编根本就不知道:设计相关文件资料是需要从一开始就打包分类整理,以确保其他团队成员能够随时查看和取用. 而且重点

代码整洁之道,新手常犯的错误。

工作几年来,见过很多糟糕的代码.It是人口流动性很大的行业,如果不注重质量,那就是无数个坑.当需求变动,去改一段很长很糟糕第n手的代码时,内心是崩溃的,这就是国内IT的现状.下面是几个写出优质代码的好习惯,大部分公司没有硬性要求,所以很多人常犯这些错误.     1.形参的命名和数量. 变量命名的好建议是可以读出来的,有具体含义的,而不是mcount,amerber这种缩写的. 形参的数量最多控制在四个,再多就需要建对象.  2.临时变量的命名和数量. 临时变量的命名很多人不注意,比如s1,st

女性求职常犯错误你中枪了吗?

在郴州找工作的求职群中,女性也占据了主要地位.但是,女性在这个社会上貌似不是那么好找工作.在女性的观念里,她们往往很容易地认为她们的成就和技术不如男人,她们常常没有足够的信心来相信和认知自己的能力,小编身边有女性朋友也存在着一些对性别求职的错误理解.如果你是女性,你在求职时是否也犯了以下这些常见的错误? 一.不相信自己是优秀的 这个是女性最常犯的一个错误认知,以前遇到过一个在餐厅做服务员的女士,她用自己的劳动养活了自己以及他的家庭,但是当我问她,你是否觉得自己是个职场女性,她连忙否决,觉得自己只

Python新人常犯的错误有哪些?

Python 以其简单易懂的语法格式与其它语言形成鲜明对比,初学者遇到最多的问题就是不按照 Python 的规则来写,即便是有编程经验的程序员,也容易按照固有的思维和语法格式来写 Python 代码,有一个外国小伙总结了一些大家常犯的错误,我把他翻译过来并在原来的基础补充了我的一些理解,希望可以让你避开这些坑,更好的学习python. 0.忘记写冒号 在 if.elif.else.for.while.class.def 语句后面忘记添加 ":" if spam == 42 print(

数据库新手常犯的5个错误

刚做开发人员的时候,需要掌握的东西非常多.首先是编程语言本身,还有所有你用到的框架的的特定用法,之后(也可能是之前),前端开发的东西也会混进来,在开发过程中你还要考虑数据存在哪的问题. 起初,由于你有太多东西需要迅速掌握,在应用设计的过程中,会倾向于把数据库放在后面考虑(大概因为它对用户的使用体验没什么影响).结果就是在处理数据库的时候,会发现很多不好的实践.这里举几个例子. 1. Storing images 储存图片 数据库里不应该放图片.你可以做的事情并不代表你就应该去做.图片会占用数据库