Python程序员开发中常犯的10个错误


Posted in Python onJuly 07, 2014

Python是一门简单易学的编程语言,语法简洁而清晰,并且拥有丰富和强大的类库。与其它大多数程序设计语言使用大括号不一样 ,它使用缩进来定义语句块。

在平时的工作中,Python开发者很容易犯一些小错误,这些错误都很容易避免,本文总结了Python开发者最常犯的10个错误,一起来看下,不知你中枪了没有。

1.滥用表达式作为函数参数默认值

Python允许开发者指定一个默认值给函数参数,虽然这是该语言的一个特征,但当参数可变时,很容易导致混乱,例如,下面这段函数定义:

>>> 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

在上面这段代码里,一旦重复调用foo()函数(没有指定一个bar参数),那么将一直返回'bar',因为没有指定参数,那么foo()每次被调用的时候,都会赋予[]。下面来看看,这样做的结果:

>>> foo()

["baz"]

>>> foo()

["baz", "baz"]

>>> foo()

["baz", "baz", "baz"]

解决方案:

>>> def foo(bar=None):

...    if bar is None:  # or if not bar:

...        bar = []

...    bar.append("baz")

...    return bar

...

>>> foo()

["baz"]

>>> foo()

["baz"]

>>> foo()

["baz"]

2.错误地使用类变量

先看下面这个例子:

>>> class A(object):

...     x = 1

...

>>> class B(A):

...     pass

...

>>> class C(A):

...     pass

...

>>> print A.x, B.x, C.x

1 1 1

这样是有意义的:

>>> B.x = 2

>>> print A.x, B.x, C.x

1 2 1

再来一遍:
>>> A.x = 3

>>> print A.x, B.x, C.x

3 2 3

仅仅是改变了A.x,为什么C.x也跟着改变了。

在Python中,类变量都是作为字典进行内部处理的,并且遵循方法解析顺序(MRO)。在上面这段代码中,因为属性x没有在类C中发现,它会查找它的基类(在上面例子中只有A,尽管Python支持多继承)。换句话说,就是C自己没有x属性,独立于A,因此,引用 C.x其实就是引用A.x。

3.为异常指定不正确的参数

假设代码中有如下代码:

>>> 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

问题在这里,except语句并不需要这种方式来指定异常列表。然而,在Python 2.x中,except Exception,e通常是用来绑定异常里的 第二参数,好让其进行更进一步的检查。因此,在上面这段代码里,IndexError异常并没有被except语句捕获,异常最后被绑定 到了一个名叫IndexError的参数上。

在一个异常语句里捕获多个异常的正确方法是指定第一个参数作为一个元组,该元组包含所有被捕获的异常。与此同时,使用as关键字来保证最大的可移植性,Python 2和Python 3都支持该语法。

>>> try:

...     l = ["a", "b"]

...     int(l[2])

... except (ValueError, IndexError) as e:  

...     pass

...

>>>

4.误解Python规则范围

Python的作用域解析是基于LEGB规则,分别是Local、Enclosing、Global、Built-in。实际上,这种解析方法也有一些玄机,看下面这个例子:

>>> 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

许多人会感动惊讶,当他们在工作的函数体里添加一个参数语句,会在先前工作的代码里报UnboundLocalError错误( 点击这里查看更详细描述)。

在使用列表时,开发者是很容易犯这种错误的,看看下面这个例子:

>>> lst = [1, 2, 3]

>>> def foo1():

...     lst.append(5)   # This works ok...

...

>>> foo1()

>>> lst

[1, 2, 3, 5]
>>> lst = [1, 2, 3]

>>> def foo2():

...     lst += [5]      # ... but this bombs!

...

>>> foo2()

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

  File "<stdin>", line 2, in foo

UnboundLocalError: local variable 'lst' referenced before assignment

为什么foo2失败而foo1运行正常?

答案与前面那个例子是一样的,但又有一些微妙之处。foo1没有赋值给lst,而foo2赋值了。lst += [5]实际上就是lst = lst + [5],试图给lst赋值(因此,假设Python是在局部作用域里)。然而,我们正在寻找指定给lst的值是基于lst本身,其实尚未确定。

5.修改遍历列表

下面这段代码很明显是错误的:

>>> 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

...

Traceback (most recent call last):

     File "<stdin>", line 2, in <module>

IndexError: list index out of range

在遍历的时候,对列表进行删除操作,这是很低级的错误。稍微有点经验的人都不会犯。

对上面的代码进行修改,正确地执行:

>>> 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, 2, 4, 6, 8]

6.如何在闭包中绑定变量

看下面这个例子:

>>> def create_multipliers():

...     return [lambda x : i * x for i in range(5)]

>>> for multiplier in create_multipliers():

...     print multiplier(2)

...

你期望的结果是:

0

2

4

6

8

实际上:

8

8

8

8

8

 是不是非常吃惊!出现这种情况主要是因为Python的后期绑定行为,该变量在闭包中使用的同时,内部函数又在调用它。

解决方案:

>>> def create_multipliers():

...     return [lambda x, i=i : i * x for i in range(5)]

...

>>> for multiplier in create_multipliers():

...     print multiplier(2)

...

0

2

4

6

8

7.创建循环模块依赖关系

假设有两个文件,a.py和b.py,然后各自导入,如下:

在a.py中:

import b
def f():

    return b.x

 

print f()

在b.py中:

import a
x = 1
def g():

    print a.f()

首先,让我们试着导入a.py:
>>> import a

1

可以很好地工作,也许你会感到惊讶。毕竟,我们确实在这里做了一个循环导入,难道不应该有点问题吗?

仅仅存在一个循环导入并不是Python本身问题,如果一个模块被导入,Python就不会试图重新导入。根据这一点,每个模块在试图访问函数或变量时,可能会在运行时遇到些问题。

当我们试图导入b.py会发生什么(先前没有导入a.py):

>>> import b

Traceback (most recent call last):

     File "<stdin>", line 1, in <module>

     File "b.py", line 1, 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的过程中还要试图导入a.py,这样就要调用f(),并且试图访问b.x。但是b.x并未被定义。

可以这样解决,仅仅修改b.py导入到a.py中的g()函数:

x = 1

def g():

    import a # This will be evaluated only when g() is called

    print a.f()

无论何时导入,一切都可以正常运行:

>>> 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'

8.与Python标准库模块名称冲突

Python拥有非常丰富的模块库,并且支持“开箱即用”。因此,如果不刻意避免,很容易发生命名冲突事件。例如,在你的代码中可能有一个email.py的模块,由于名称一致,它很有可能与Python自带的标准库模块发生冲突。

9.未按规定处理Python2.x和Python3.x之间的区别

看一下foo.py:

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()

在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

解决方案:

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()

 在Py3k中运行结果:

$ python3 foo.py 1

key error

1

$ python3 foo.py 2

value error

2

在 Python招聘指南里有许多关于Python 2与Python 3在移植代码时需要关注的注意事项与讨论,大家可以前往看看。

10.滥用__del__方法

比如这里有一个叫mod.py的文件:

import foo

class Bar(object):

        ...

    def __del__(self):

        foo.cleanup(self.myhandle)

下面,你在another_mod.py文件里执行如下操作:

import mod

mybar = mod.Bar()

 你会获得一个AttributeError异常。

至于为什么会出现该异常,点击这里查看详情。当解释器关闭时,该模块的全局变量全部设置为None。因此,在上面这个例子里,当__del__被调用时,foo已经全部被设置为None。

一个很好的解决办法是使用atexit.register()代替。顺便说一句,当程序执行完成后,您注册的处理程序会在解释器关闭之前停止 工作。

修复上面问题的代码:

import foo

import atexit
def cleanup(handle):

    foo.cleanup(handle)


class Bar(object):

    def __init__(self):

        ...

        atexit.register(cleanup, self.myhandle)

在程序的正常终止的前提下,这个实现提供了一个整洁可靠的方式调用任何需要清理的功能。

总结

Python是一款强大而灵活的编程语言,并且带有许多机制和模式来大大提高工作效率。正如任何一门语言或软件工具一样,人们对其能力都会存在一个限制性地理解或欣赏,有些是弊大于利,有些时候反而会带来一些陷进。 体会一名语言的细微之处,理解一些常见的陷阱,有助于你在开发者的道路上走的更远。

Python 相关文章推荐
python实现的防DDoS脚本
Feb 08 Python
Python写的英文字符大小写转换代码示例
Mar 06 Python
python显示生日是星期几的方法
May 27 Python
Python+PIL实现支付宝AR红包
Feb 09 Python
python实现多层感知器
Jan 18 Python
pandas把所有大于0的数设置为1的方法
Jan 26 Python
Python提取特定时间段内数据的方法实例
Apr 01 Python
python对验证码降噪的实现示例代码
Nov 12 Python
python selenium实现发送带附件的邮件代码实例
Dec 10 Python
django model通过字典更新数据实例
Apr 01 Python
Selenium向iframe富文本框输入内容过程图解
Apr 10 Python
python实现简单文件读写函数
Feb 25 Python
python采用requests库模拟登录和抓取数据的简单示例
Jul 05 #Python
浅析python 中__name__ = '__main__' 的作用
Jul 05 #Python
python在windows下实现备份程序实例
Jul 04 #Python
python调用短信猫控件实现发短信功能实例
Jul 04 #Python
Python实现类继承实例
Jul 04 #Python
Django集成百度富文本编辑器uEditor攻略
Jul 04 #Python
一个小示例告诉你Python语言的优雅之处
Jul 04 #Python
You might like
一段防盗连的PHP代码
2006/12/06 PHP
php 智能404跳转代码,适合换域名没改变目录的网站
2010/06/04 PHP
ionCube 一款类似zend的PHP加密/解密工具
2010/07/25 PHP
PHP处理JSON字符串key缺少双引号的解决方法
2014/09/16 PHP
ThinkPHP控制器间实现相互调用的方法
2014/10/31 PHP
PHP基于单例模式实现的数据库操作基类
2016/01/15 PHP
PHP删除二维数组中相同元素及数组重复值的方法示例
2017/05/05 PHP
jQuery setTimeout()函数使用方法
2013/04/07 Javascript
jQuery的animate函数实现图文切换动画效果
2015/05/03 Javascript
JS实现网页上随滚动条滚动的层效果代码
2015/11/04 Javascript
点评js异步加载的4种方式
2015/12/22 Javascript
JS实现密码框的显示密码和隐藏密码功能示例
2016/12/26 Javascript
Vue Cli3 创建项目的方法步骤
2018/10/15 Javascript
axios如何利用promise无痛刷新token的实现方法
2019/08/27 Javascript
JS如何把字符串转换成json
2020/02/21 Javascript
使用Vue 自定义文件选择器组件的实例代码
2020/03/04 Javascript
python统计日志ip访问数的方法
2015/07/06 Python
Python函数装饰器实现方法详解
2018/12/22 Python
Python-Tkinter Text输入内容在界面显示的实例
2019/07/12 Python
Django+RestFramework API接口及接口文档并返回json数据操作
2020/07/12 Python
英国山地公路自行车商店:Tweeks Cycles
2018/03/16 全球购物
HEMA英国:荷兰原创设计
2018/08/28 全球购物
软件测试面试题
2015/10/21 面试题
毕业生个人求职的自我评价
2013/10/28 职场文书
心理学专业毕业生推荐信范文
2013/11/21 职场文书
学习十八大精神心得体会
2013/12/31 职场文书
祖国在我心中的演讲稿
2014/05/04 职场文书
机关副主任个人四风问题整改措施
2014/09/26 职场文书
领导班子在批评与自我批评座谈会上的发言
2014/09/28 职场文书
2014乡镇机关党员个人对照检查材料思想汇报
2014/10/09 职场文书
家庭财产分割协议范文
2014/11/24 职场文书
2014年世界艾滋病日演讲稿
2014/11/28 职场文书
小学教师工作总结2015
2015/04/07 职场文书
上诉答辩状范文
2015/05/22 职场文书
十一月早安语录:把心放轻,人生就是一朵自在的云
2019/11/04 职场文书
你知道哪几种MYSQL的连接查询
2021/06/03 MySQL