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使用socket连接远程服务器的方法
Apr 29 Python
Python简单计算数组元素平均值的方法示例
Dec 26 Python
对pycharm代码整体左移和右移缩进快捷键的介绍
Jul 16 Python
Django组件之cookie与session的使用方法
Jan 10 Python
使用Python快速制作可视化报表的方法
Feb 03 Python
python 有效的括号的实现代码示例
Nov 11 Python
pytorch实现用CNN和LSTM对文本进行分类方式
Jan 08 Python
如何通过Python3和ssl实现加密通信功能
May 09 Python
在pycharm中创建django项目的示例代码
May 28 Python
Python内置方法和属性应用:反射和单例(推荐)
Jun 19 Python
使用Python爬取小姐姐图片(beautifulsoup法)
Feb 11 Python
pyqt5打包成exe可执行文件的方法
May 14 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
四月新番又没了,《Re:从零开始的异世界生活》第二季延期至7月播出
2020/05/06 日漫
PHP+Apache+Mysql环境搭建教程
2016/08/01 PHP
firefo xml 读写实现js代码
2009/06/11 Javascript
在Javascript里访问SharePoint列表数据的实现方法
2011/05/22 Javascript
Extjs中ComboBox加载并赋初值的实现方法
2012/03/22 Javascript
事件委托与阻止冒泡阻止其父元素事件触发
2014/09/02 Javascript
jQuery使用$.get()方法从服务器文件载入数据实例
2015/03/25 Javascript
JavaScript实现的简单幂函数实例
2015/04/17 Javascript
学习Angular中作用域需要注意的坑
2016/08/17 Javascript
node学习记录之搭建web服务器教程
2017/02/16 Javascript
深入探究node之Transform
2017/07/20 Javascript
浅谈react 同构之样式直出
2017/11/07 Javascript
javascript中的隐式调用
2018/02/10 Javascript
用react-redux实现react组件之间数据共享的方法
2018/06/08 Javascript
详解VUE中常用的几种import(模块、文件)引入方式
2018/07/03 Javascript
vscode下vue项目中eslint的使用方法
2019/01/13 Javascript
vue+koa2实现session、token登陆状态验证的示例
2019/08/30 Javascript
在vue中通过render函数给子组件设置ref操作
2020/11/17 Vue.js
[01:51]2014DOTA2西雅图邀请赛 MVP 外卡赛black场间采访
2014/07/09 DOTA
Python六大开源框架对比
2015/10/19 Python
python学习之面向对象【入门初级篇】
2017/01/21 Python
python对list中的每个元素进行某种操作的方法
2018/06/29 Python
10个Python小技巧你值得拥有
2018/09/29 Python
Python 利用scrapy爬虫通过短短50行代码下载整站短视频
2018/10/29 Python
Python中的类与类型示例详解
2019/07/10 Python
简单了解python装饰器原理及使用方法
2019/12/18 Python
利用Python脚本批量生成SQL语句
2020/03/04 Python
python 实现读取csv数据,分类求和 再写进 csv
2020/05/18 Python
倩碧美国官网:Clinique美国
2016/07/20 全球购物
美国知名的网上鞋类及相关服装零售商:Shoes.com
2017/05/06 全球购物
函授药学自我鉴定
2014/02/07 职场文书
大学生村官个人总结
2015/02/15 职场文书
归途列车观后感
2015/06/17 职场文书
Python基础之数据类型知识汇总
2021/05/18 Python
详解使用 CSS prefers-* 规范提升网站的可访问性与健壮性
2021/05/25 HTML / CSS
通过T-SQL语句创建游标与实现数据库加解密功能
2022/03/16 SQL Server