Python 程序员容易犯的10个错误

对python这个高级语言感兴趣的小伙伴,下面一起跟随编程之家 jb51.cc的小编两巴掌来看看吧!

关于Python

Python是一种解释性、面向对象并具有动态语义的高级程序语言。它内建了高级的数据结构,结合了动态类型和动态绑定的优点,这使得它在快速应用开发中非常有吸引力,并且可作为脚本或胶水语言来连接现有的组件或服务。Python支持模块和包,从而鼓励了程序的模块化和代码重用。

关于这篇文章

Python简单易学的语法可能会使Python开发者–尤其是那些编程的初学者–忽视了它的一些微妙的地方并低估了这门语言的能力。

有鉴于此,本文列出了一个“10强”名单,枚举了甚至是高级Python开发人员有时也难以捕捉的错误。

常见错误 1: 滥用表达式作为函数参数的默认值

Python允许为函数的参数提供默认的可选值。尽管这是语言的一大特色,但是它可能会导致一些易变默认值的混乱。例如,看一下这个Python函数的定义:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

>>> def foo(bar=[]):        # bar is optional and defaults to [] if not specified  
...    bar.append(baz)    # but this line could be problematic,as we'll see...  
...    return bar

# End www.jb51.cc

一个常见的错误是认为在函数每次不提供可选参数调用时可选参数将设置为默认指定值。在上面的代码中,例如,人们可能会希望反复(即不明确指定bar参数)地调用foo()时总返回'baz',由于每次foo()调用时都假定(不设定bar参数)bar被设置为[](即一个空列表)。

但是让我们看一下这样做时究竟会发生什么:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

>>> foo()  
[baz]>>> foo()  
[baz, baz]>>> foo()  
[baz, baz, baz]

# End www.jb51.cc

耶?为什么每次foo()调用时都要把默认值baz追加到现有列表中而不是创建一个新的列表呢?

答案是函数参数的默认值只会评估使用一次—在函数定义的时候。因此,bar参数在初始化时为其默认值(即一个空列表),即foo()首次定义的时候,但当调用foo()时(即,不指定bar参数时)将继续使用bar原本已经初始化的参数。

下面是一个常见的解决方法:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

>>> def foo(bar=None):  
...    if bar is None:        # or if not bar:  
...        bar = []  
...    bar.append(baz)  
...    return bar  
...  
>>> foo()  
[baz]  
>>> foo()  
[baz]  
>>> foo()  
[baz]

# End www.jb51.cc

常见错误 2: 错误地使用类变量

考虑一下下面的例子:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

>>> class A(object):  
...     x = 1 
...  
>>> class B(A):  
...     pass 
...  
>>> class C(A):  
...     pass 
...  
>>> print A.x,B.x,C.x  
1 1 1

# End www.jb51.cc

常规用一下。


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

>>> B.x = 2 
>>> print A.x,C.x  
1 2 1

# End www.jb51.cc

嗯,再试一下也一样。


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

>>> A.x = 3 
>>> print A.x,C.x  
3 2 3

# End www.jb51.cc

什么 $%#!&?? 我们只改了A.x,为什么C.x也改了?

在Python中,类变量在内部当做字典来处理,其遵循常被引用的方法解析顺序(MRO)。所以在上面的代码中,由于class C中的x属性没有找到,它会向上找它的基类(尽管Python支持多重继承,但上面的例子中只有A)。换句话说,class C中没有它自己的x属性,其独立于A。因此,C.x事实上是A.x的引用。

常见错误 3: 为 except 指定错误的参数

假设你有如下一段代码:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

>>> try:  
...     l = [a,b]  
...     int(l[2])  
... except ValueError,IndexError:  # To catch both exceptions,right?  
...     pass 
...  
Traceback (most recent call last):  
  File <stdin>,line 3,in <module>  
IndexError: list index out of range

# End www.jb51.cc

这里的问题在于 except 语句并不接受以这种方式指定的异常列表。相反,在Python 2.x中,使用语法 except Exception,e 是将一个异常对象绑定到第二个可选参数(在这个例子中是 e)上,以便在后面使用。所以,在上面这个例子中,IndexError 这个异常并不是被except语句捕捉到的,而是被绑定到一个名叫 IndexError的参数上时引发的。

在一个except语句中捕获多个异常的正确做法是将第一个参数指定为一个含有所有要捕获异常的元组。并且,为了代码的可移植性,要使用as关键词,因为Python 2 和Python 3都支持这种语法:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

>>> try:  
...     l = [a,b]  
...     int(l[2])  
... except (ValueError,IndexError) as e:    
...     pass 
...  
>>>

# End www.jb51.cc

常见错误 4: 不理解Python的作用域

Python是基于 LEGB 来进行作用于解析的,LEGB 是 Local,Enclosing,Global,Built-in 的缩写。看起来“见文知意”,对吗?实际上,在Python中还有一些需要注意的地方,先看下面一段代码:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

>>> x = 10 
>>> def foo():  
...     x += 1 
...     print x  
...  
>>> foo()  
Traceback (most recent call last):  
  File <stdin>,line 1,in <module>  
  File <stdin>,line 2,in foo  
UnboundLocalError: local variable 'x' referenced before assignment

# End www.jb51.cc

这里出什么问题了?

上面的问题之所以会发生是因为当你给作用域中的一个变量赋值时,Python 会自动的把它当做是当前作用域的局部变量,从而会隐藏外部作用域中的同名变量。

很多人会感到很吃惊,当他们给之前可以正常运行的代码的函数体的某个地方添加了一句赋值语句之后就得到了一个 UnboundLocalError 的错误。 (你可以在这里了解到更多)

尤其是当开发者使用 lists 时,这个问题就更加常见. 请看下面这个例子:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

>>> lst = [1,2,3]  
>>> def foo1():  
...     lst.append(5)   # 没有问题...  
...  
>>> foo1()  
>>> lst  
[1,3,5]  
 
>>> lst = [1,3]  
>>> def foo2():  
...     lst += [5]      # ... 但是这里有问题!  
...  
>>> foo2()

# End www.jb51.cc

 

Traceback (most recent call last):

File <stdin>,in <module>

File <stdin>,in foo

UnboundLocalError: local variable 'lst' referenced before assignment

嗯?为什么 foo2 报错,而foo1没有问题呢?

原因和之前那个例子的一样,不过更加令人难以捉摸。foo1 没有对 lst 进行赋值操作,而 foo2 做了。要知道, lst += [5] 是 lst = lst + [5] 的缩写,我们试图对 lst 进行赋值操作(Python把他当成了局部变量)。此外,我们对 lst 进行的赋值操作是基于 lst 自身(这再一次被Python当成了局部变量),但此时还未定义。因此出错!

常见错误 5:当迭代时修改一个列表(List)

下面代码中的问题应该是相当明显的:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

>>> odd = lambda x : bool(x % 2)  
>>> numbers = [n for n in range(10)]  
>>> for i in range(len(numbers)):  
...     if odd(numbers[i]):  
...         del numbers[i]  # BAD: Deleting item from a list while iterating over it  
...

# End www.jb51.cc

Traceback (most recent call last):

File <stdin>,in <module>

IndexError: list index out of range

当迭代的时候,从一个 列表 (List)或者数组中删除元素,对于任何有经验的开发者来说,这是一个众所周知的错误。尽管上面的例子非常明显,但是许多高级开发者在更复杂的代码中也并非是故意而为之的。

幸运的是,Python包含大量简洁优雅的编程范例,若使用得当,能大大简化和精炼代码。这样的好处是能得到更简化和更精简的代码,能更好的避免程序中出现当迭代时修改一个列表(List)这样的bug。一个这样的范例是递推式列表(list comprehensions)。而且,递推式列表(list comprehensions)针对这个问题是特别有用的,通过更改上文中的实现,得到一段极佳的代码:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

>>> odd = lambda x : bool(x % 2)  
>>> numbers = [n for n in range(10)]  
>>> numbers[:] = [n for n in numbers if not odd(n)]  # ahh,the beauty of it all  
>>> numbers  
[0,4,6,8]

# End www.jb51.cc

常见错误 6: 不明白Python在闭包中是如何绑定变量的

看下面这个例子:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

>>> def create_multipliers():  
...     return [lambda x : i * x for i in range(5)]  
>>> for multiplier in create_multipliers():  
...     print multiplier(2)  
...

# End www.jb51.cc

你也许希望获得下面的输出结果:

0

2

4

6

8

但实际的结果却是:

8

8

8

8

8

惊讶吧!

这之所以会发生是由于Python中的“后期绑定”行为——闭包中用到的变量只有在函数被调用的时候才会被赋值。所以,在上面的代码中,任何时候,当返回的函数被调用时,Python会在该函数被调用时的作用域中查找 i 对应的值(这时,循环已经结束,所以 i 被赋上了最终的值——4)。

解决的方法有一点hack的味道:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

>>> def create_multipliers():  
...     return [lambda x,i=i : i * x for i in range(5)]  
...  
>>> for multiplier in create_multipliers():  
...     print multiplier(2)  
...

# End www.jb51.cc

0

2

4

6

8

在这里,我们利用了默认参数来生成一个匿名的函数以便实现我们想要的结果。有人说这个方法很巧妙,有人说它难以理解,还有人讨厌这种做法。但是,如果你是一个 Python 开发者,理解这种行为很重要。

常见错误 7: 创建循环依赖模块

让我们假设你有两个文件,a.py 和 b.py,他们之间相互引用,如下所示:

a.py:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

import b  
def f():  
    return b.x    
print f()

# End www.jb51.cc

b.py:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

import a  
x = 1 
def g():  
    print a.f()

# End www.jb51.cc

首先,让我们尝试引入 a.py:

>>> import a

1

可以正常工作。这也许是你感到很奇怪。毕竟,我们确实在这里引入了一个循环依赖的模块,我们推测这样会出问题的,不是吗?

答案就是在Python中,仅仅引入一个循环依赖的模块是没有问题的。如果一个模块已经被引入了,Python并不会去再次引入它。但是,根据每个模块要访问其他模块中的函数和变量位置的不同,就很可能会遇到问题。

所以,回到我们这个例子,当我们引入 a.py 时,再引入 b.py 不会产生任何问题,因为当引入的时候,b.py 不需要 a.py 中定义任何东西。b.py 中唯一引用 a.py 中的东西是调用 a.f()。 但是那个调用是发生在g() 中的,并且 a.py 和 b.py 中都没有调用 g()。所以运行正常。

但是,如果我们尝试去引入b.py 会发生什么呢?(在这之前不引入a.py),如下所示:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

>>> import b

# End www.jb51.cc

Traceback (most recent call last):

File <stdin>,in <module>

File b.py,in <module>

import a

File a.py,line 6,in <module>

print f()

File a.py,line 4,in f

return b.x

AttributeError: 'module' object has no attribute 'x'

啊哦。 出问题了!此处的问题是,在引入b.py的过程中,Python尝试去引入 a.py,但是a.py 要调用f(),而f() 有尝试去访问 b.x。但是此时 b.x 还没有被定义呢。所以发生了 AttributeError 异常。

至少,解决这个问题很简单,只需修改b.py,使其在g()中引入 a.py:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

x = 1 
def g():  
    import a    # 只有当g()被调用的时候才会引入a  
    print a.f()

# End www.jb51.cc

现在,当我们再引入b,没有任何问题:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

>>> import b  
>>> b.g()  
1    # Printed a first time since module 'a' calls 'print f()' at the end  
1    # Printed a second time,this one is our call to 'g'

# End www.jb51.cc

# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

常见错误 8: 与Python标准库中的模块命名冲突

# End www.jb51.cc

Python一个令人称赞的地方是它有丰富的模块可供我们“开箱即用”。但是,如果你没有有意识的注意的话,就很容易出现你写的模块和Python自带的标准库的模块之间发生命名冲突的问题(如,你也许有一个叫 email.py 的模块,但这会和标准库中的同名模块冲突)。

这可能会导致很怪的问题,例如,你引入了另一个模块,但这个模块要引入一个Python标准库中的模块,由于你定义了一个同名的模块,就会使该模块错误的引入了你的模块,而不是 stdlib 中的模块。这就会出问题了。

因此,我们必须要注意这个问题,以避免使用和Python标准库中相同的模块名。修改你包中的模块名要比通过 Python Enhancement Proposal (PEP) 给Python提建议来修改标准库的模块名容易多了。

常见错误 #9: 未能解决Python 2和Python 3之间的差异

请看下面这个 filefoo.py:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

import sys  
def bar(i):  
    if i == 1:  
        raise KeyError(1)  
    if i == 2:  
        raise ValueError(2)  
 
def bad():  
    e = None 
    try:  
        bar(int(sys.argv[1]))  
    except KeyError as e:  
        print('key error')  
    except ValueError as e:  
        print('value error')  
    print(e)  
 
bad()

# End www.jb51.cc

在Python 2中运行正常:

$ python foo.py 1

key error

1

$ python foo.py 2

value error

2

但是,现在让我们把它在Python 3中运行一下:

$ python3 foo.py 1

key error

Traceback (most recent call last):

File foo.py,line 19,in <module>

bad()

File foo.py,line 17,in bad

print(e)

UnboundLocalError: local variable 'e' referenced before assignment

出什么问题了? “问题”就是,在 Python 3 中,异常的对象在 except 代码块之外是不可见的。(这样做的原因是,它将保存一个对内存中堆栈帧的引用周期,直到垃圾回收器运行并且从内存中清除掉引用。了解更多技术细节请参考这里) 。

一种解决办法是在 except 代码块的外部作用域中定义一个对异常对象的引用,以便访问。下面的例子使用了该方法,因此最后的代码可以在Python 2 和 Python 3中运行良好。


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

import sys  
def bar(i):  
    if i == 1:  
        raise KeyError(1)  
    if i == 2:  
        raise ValueError(2)  
def good():  
    exception = None 
    try:  
        bar(int(sys.argv[1]))  
    except KeyError as e:  
        exception = e  
        print('key error')  
    except ValueError as e:  
        exception = e  
        print('value error')  
    print(exception)  
 
good()

# End www.jb51.cc

在Py3k中运行:

$ python3 foo.py 1

key error

1

$ python3 foo.py 2

value error

2

正常!

(顺便提一下,我们的 Python Hiring Guide 讨论了当我们把代码从Python 2 迁移到 Python 3时的其他一些需要知道的重要差异。)

常见错误 10: 误用__del__方法

假设你有一个名为 calledmod.py 的文件:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

import foo  
class Bar(object):  
           ...  
    def __del__(self):  
        foo.cleanup(self.myhandle)

# End www.jb51.cc

并且有一个名为 another_mod.py 的文件:

import mod

mybar = mod.Bar()

你会得到一个 AttributeError 的异常。

为什么呢?因为,正如这里所说,当解释器退出的时候,模块中的全局变量都被设置成了 None。所以,在上面这个例子中,当 __del__ 被调用时,foo 已经被设置成了None。

解决方法是使用 atexit.register() 代替。用这种方式,当你的程序结束执行时(意思是正常退出),你注册的处理程序会在解释器退出之前执行。

了解了这些,我们可以将上面 mod.py 的代码修改成下面的这样:


# @param Python 程序员经常犯的 10 个错误
# @author 编程之家 jb51.cc|jb51.cc 

import foo  
import atexit  
def cleanup(handle):  
    foo.cleanup(handle)  
class Bar(object):  
    def __init__(self):  
        ...  
        atexit.register(cleanup,self.myhandle)

# End www.jb51.cc

这种实现方式提供了一个整洁并且可信赖的方法用来在程序退出之前做一些清理工作。很显然,它是由foo.cleanup 来决定对绑定在 self.myhandle 上对象做些什么处理工作的,但是这就是你想要的。

总结

Python是一门强大的并且很灵活的语言,它有很多机制和语言规范来显著的提高你的生产力。和其他任何一门语言或软件一样,如果对它能力的了解有限,这很可能会给你带来阻碍,而不是好处。正如一句谚语所说的那样 “knowing enough to be dangerous”(译者注:意思是自以为已经了解足够了,可以做某事了,但其实不是)。

熟悉Python的一些关键的细微之处,像本文中所提到的那些(但不限于这些),可以帮助我们更好的去使用语言,从而避免一些常见的陷阱。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Python中的函数(二) 在上一篇文章中提到了Python中函数的定义和使用,在这篇文章里我们来讨论下关于函数的一些更深的话题。在学习C语言函数的时候,遇到的问题主要有形参实参的区别、参数的传递和改变、变量的作用域。同样在Python中,关于对函数的理解和使用也存在这些问题。下面来逐一讲解。一.函
Python中的字符串 可能大多数人在学习C语言的时候,最先接触的数据类型就是字符串,因为大多教程都是以&quot;Hello world&quot;这个程序作为入门程序,这个程序中要打印的&quot;Hello world&quot;就是字符串。如果你做过自然语言处理方面的研究,并且用Python
Python 面向对象编程(一) 虽然Python是解释性语言,但是它是面向对象的,能够进行对象编程。下面就来了解一下如何在Python中进行对象编程。一.如何定义一个类 在进行python面向对象编程之前,先来了解几个术语:类,类对象,实例对象,属性,函数和方法。 类是对现实世界中一些事物的封装,
Python面向对象编程(二) 在前面一篇文章中谈到了类的基本定义和使用方法,这只体现了面向对象编程的三大特点之一:封装。下面就来了解一下另外两大特征:继承和多态。 在Python中,如果需要的话,可以让一个类去继承一个类,被继承的类称为父类或者超类、也可以称作基类,继承的类称为子类。并且Pytho
Python中的函数(一) 接触过C语言的朋友对函数这个词肯定非常熟悉,无论在哪门编程语言当中,函数(当然在某些语言里称作方法,意义是相同的)都扮演着至关重要的角色。今天就来了解一下Python中的函数用法。一.函数的定义 在某些编程语言当中,函数声明和函数定义是区分开的(在这些编程语言当中函数声明
在windows下如何快速搭建web.py开发框架 用Python进行web开发的话有很多框架供选择,比如最出名的Django,tornado等,除了这些框架之外,有一个轻量级的框架使用起来也是非常方便和顺手,就是web.py。它由一名黑客所创建,但是不幸的是这位创建者于2013年自杀了。据说现在由
将Sublime Text 2搭建成一个好用的IDE 说起编辑器,可能大部分人要推荐的是Vim和Emacs,本人用过Vim,功能确实强大,但是不是很习惯,之前一直有朋友推荐SUblime Text 2这款编辑器,然后这段时间就试了一下,就深深地喜欢上这款编辑器了...
Python中的模块 有过C语言编程经验的朋友都知道在C语言中如果要引用sqrt这个函数,必须用语句&quot;#include&lt;math.h&gt;&quot;引入math.h这个头文件,否则是无法正常进行调用的。那么在Python中,如果要引用一些内置的函数,该怎么处理呢?在Python中
Python的基础语法 在对Python有了基础的认识之后,下面来了解一下Python的基础语法,看看它和C语言、java之间的基础语法差异。一.变量、表达式和语句 Python中的语句也称作命令,比如print &quot;hello python&quot;这就是一条语句。 表达式,顾名思义,是
Eclipse+PyDevʽjango+Mysql搭建Python web开发环境 Python的web框架有很多,目前主流的有Django、Tornado、Web.py等,最流行的要属Django了,也是被大家最看好的框架之一。下面就来讲讲如何搭建Django的开发环境。一.准备工作 需要下载的
在windows下安装配置Ulipad 今天推荐一款轻便的文本编辑器Ulipad,用来写一些小的Python脚本非常方便。 Ulipad下载地址: https://github.com/limodou/ulipad http://files.cnblogs.com/dolphin0520/u...
Python中的函数(三) 在前面两篇文章中已经探讨了函数的一些相关用法,下面一起来了解一下函数参数类型的问题。在C语言中,调用函数时必须依照函数定义时的参数个数以及类型来传递参数,否则将会发生错误,这个是严格进行规定的。然而在Python中函数参数定义和传递的方式相比而言就灵活多了。一.函数参数的
在Notepad++中搭配Python开发环境 Python在最近几年一度成为最流行的语言之一,不仅仅是因为它简洁明了,更在于它的功能之强大。它不仅能够完成一般脚本语言所能做的事情,还能很方便快捷地进行大规模的项目开发。在学习Python之前我们来看一下Python的历史由来,&quot;Pytho
Python中的条件选择和循环语句 同C语言、Java一样,Python中也存在条件选择和循环语句,其风格和C语言、java的很类似,但是在写法和用法上还是有一些区别。今天就让我们一起来了解一下。一.条件选择语句 Python中条件选择语句的关键字为:if 、elif 、else这三个。其基本形式如
关于raw_input( )和sys.stdin.readline( )的区别 之前一直认为用raw_input( )和sys.stdin.readline( )来获取输入的效果完全相同,但是最近在写程序时有类似这样一段代码:import sysline = sys.stdin.readline()
初识Python 跟学习所有的编程语言一样,首先得了解这门语言的编程风格和最基础的语法。下面就让我们一起来了解一下Python的编程风格。1.逻辑行与物理行 在Python中有逻辑行和物理行这个概念,物理行是指在编辑器中实际看到的一行,逻辑行是指一条Python语句。在Python中提倡一个物理行只
当我们的代码是有访问网络相关的操作时,比如http请求或者访问远程数据库,经常可能会发生一些错误,有些错误可能重新去发送请求就会成功,本文分析常见可能需要重试的场景,并最后给出python代码实现。
1.经典迭代器 2.将Sentence中的__iter__改成生成器函数 改成生成器后用法不变,但更加简洁。 3.惰性实现 当列表比较大,占内存较大时,我们可以采用惰性实现,每次只读取一个元素到内存。 或者使用更简洁的生成器表达式 4.yield from itertools模块含有大量生成器函数可
本文介绍简单介绍socket的常用函数,并以python-kafka中的源码socketpair为例,来讲解python socket的运用
python实践中经常出现编码相关的异常,大多网上找资料而没有理解原理,导致一次次重复错误。本文对常用Unicode、UTF-8、GB2312编码的原理进行介绍,接着介绍了python字符类型unicode和str以及常见编解码错误UnicodeEncodeError和UnicodeDEcodeEr