python新手常犯错误
来源:互联网 发布:qq飞车s车数据对比 编辑:程序博客网 时间:2024/04/30 18:03
转自:http://blog.jobbole.com/42706/
Python 新手常犯错误(第一部分)
【感谢@贱圣OMG 的热心翻译。如果其他朋友也有不错的原创或译文,可以尝试推荐给伯乐在线。】
在之前几个月里,我教一些不了解Python的孩子来慢慢熟悉这门语言。渐渐地,我发现了一些几乎所有Python初学者都会犯的错误,所以我决定跟来跟大家分享我的建议。这个系列的每个部分都会关注不同的常见错误,描述如何产生这种错误的,并且提供解决的方法。
用一个可变的值作为默认值
这是一个绝对值得放在第一个来说的问题。不仅仅是因为产生这种BUG的原因很微妙,而且这种问题也很难检查出来。思考一下下面的代码片段:
def
foo(numbers
=
[]):
numbers.append(
9
)
print
numbers
在这里,我们定义了一个 list (默认为空),给它加入9并且打印出来。
>>> foo()
[
9
]
>>> foo(numbers
=
[
1
,
2
])
[
1
,
2
,
9
]
>>> foo(numbers
=
[
1
,
2
,
3
])
[
1
,
2
,
3
,
9
]
看起来还行吧?可是当我们不输入number 参数来调用 foo 函数时,神奇的事情发生了:
>>> foo()
# first time, like before
[
9
]
>>> foo()
# second time
[
9
,
9
]
>>> foo()
# third time...
[
9
,
9
,
9
]
>>> foo()
# WHAT IS THIS BLACK MAGIC?!
[
9
,
9
,
9
,
9
]
那么,这是神马情况?直觉告诉我们无论我们不输入 number 参数调用 foo 函数多少次,这里的9应该被分配进了一个空的 list。这是错的!在Python里,函数的默认值实在函数定义的时候实例化的,而不是在调用的时候。
那么我们仍然会问,为什么在调用函数的时候这个默认值却被赋予了不同的值?因为在你每次给函数指定一个默认值的时候,Python都会存储这个值。如果在调用函数的时候重写了默认值,那么这个存储的值就不会被使用。当你不重写默认值的时候,那么Python就会让默认值引用存储的值(这个例子里的numbers)。它并不是将存储的值拷贝来为这个变量赋值。这个概念可能对初学者来说,理解起来会比较吃力,所以可以这样来理解:有两个变量,一个是内部的,一个是当前运行时的变量。现实就是我们有两个变量来用相同的值进行交互,所以一旦 numbers 的值发生变化,也会改变Python里面保存的初始值的记录。
那么解决方案如下:
def
foo(numbers
=
None
):
if
numbers
is
None
:
numbers
=
[]
numbers.append(
9
)
print
numbers
通常,当人们听到这里,大家会问另一个关于默认值的问题。思考下面的程序:
def
foo(count
=
0
):
count
+
=
1
print
count
当我们运行它的时候,其结果完全是我们期望的:
>>> foo()
1
>>> foo()
1
>>> foo(
2
)
3
>>> foo(
3
)
4
>>> foo()
1
这又是为啥呢?其秘密不在与默认值被赋值的时候,而是这个默认值本身。整型是一种不可变的变量。跟 list 类型不同,在函数执行的过程中,整型变量是不能被改变的。当我们执行 count+=1 这句话时,我们并没有改变 count 这个变量原有的值。而是让 count 指向了不同的值。可是,当我们执行 numbers.append(9) 的时候,我们改变了原有的 list 。因而导致了这种结果。
下面是在函数里使用默认值时会碰到的另一种相同问题:
def
print_now(now
=
time.time()):
print
now
跟前面一样,time.time() 的值是可变的,那么它只会在函数定义的时候计算,所以无论调用多少次,都会返回相同的事件 — 这里输出的事件是程序被Python解释运行的时间。
>>> print_now()
1373121487.91
>>> print_now()
1373121487.91
>>> print_now()
1373121487.91
* 这个问题和它的解决方案在 Python 2.x 和 3.x 里都是类似的,在Python 3.x 里面唯一的不同,是里面的print 表达式应该是函数调用的方式(print(numbers))。
* 大家应该注意到我在解决方案里用了 if numbers is None 而不是 if not numbers 。这是另一种常见的错误,我准备在接下来的文章里面介绍。
转自:http://blog.jobbole.com/43826/
Python 新手常犯错误(第二部分)
作用域
在这篇文章里,我们来关注作用域在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
- python新手常犯错误
- Python 新手常犯错误
- python新手常犯错误
- python新手常犯错误
- Python 新手常犯错误(第一部分)
- Python 新手常犯错误(第二部分)
- Python 新手常犯错误(一)
- Python 新手常犯错误(二)
- Python常犯错误集合
- python 读写文件 个人 常犯错误 注意事项
- Python之def使用常犯错误总结
- 常犯错误
- 网站优化:新手常犯错误之优化过度
- 美迪为你总结新手站长常犯错误
- 性能测试新手常犯错误总结(六):性能监控
- 程序员常犯错误
- spring常犯错误
- Jquery 常犯错误
- Unable to find the ncurses libraries的解决办法
- DM6446的Bootloader
- Ubuntu 11.10下GRUB 2 1.99版编译安装笔记
- EL表达式中fn函数
- Davinci DM6446开发攻略——u-boot-1.3.4移植(1)
- python新手常犯错误
- TI Davinci DM6446开发攻略——UBL移植
- TI Davinci DM6446开发攻略——开发环境搭建
- Davinci DM6446开发攻略——linux-2.6.18移植
- Davinci DM6446开发攻略——DSP开发工程建立
- Davinci DM6446 Codec Engine双核通信环境的搭建
- TI Davinci DM6446开发攻略——根文件系统的裁剪和移植
- Davinci DM6446开发攻略——LINUX GPIO驱动源码移植
- Davinci DM6446开发攻略-UBOOT-2009.03移植2 nand flash的烧写