Python开发人员犯下的10个最常见的错误

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

python允许通过为函数提供默认值来指定函数参数的,但是当默认值是可变的时,就会产生一些问题:

def foo(bar=[]):
    bar.append('baz')
    return bar

上面的代码中,期望的是 foo() 重复调用(即不指定bar参数)将始终返回 'baz' ,因此假设每次 foo() 调用 bar 被设置为 []

但是,让我们来看看执行次操作时实际发生的情况:

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

咦,为什么每次调用都会默认值附加 'baz' 到现有的列表中,而不是每次都创建一个新列表?

答案就是: 函数参数的默认值仅在定义函数时计算一次。因此 bar 仅在 foo() 首次定义时将参数初始化为其默认值,但随后调用 foo() (即未指定bar参数),将继续使用 bar 最初初始化的相同列表。

仅供参考,一个常见的解决方法如下:

def foo(bar=None):
    if not bar:
        bar = []
    bar.append('baz')
    return bar

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)的方法,因此在上面的代码中,由于在C中找不到x属性,因此将在其基类中查找它。换句话数,C没有自己的x属性,因此引用C.x实际上值得是A.x。

3 错误的为异常块指定参数

假如你用一下代码:

# 这段代码是python 2.7版本的
>>> 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语句没有采用这种方式指定的异常列表,相反,在python2.x中,语法 except Exception, e 用于将异常绑定到指定的可选的第二个参数(本例中e),以用于进一步检查。结果在上面的代码中,IndexError异常没有被except语句捕获,相反,异常最终被绑定到一个名为IndexError的参数。

except语句中,捕获多个异常的正确方法是将第一个参数指定为包含要捕获所有异常的元祖,此外为了获得最大的可移植性,请使用as关键字,因为Python2和Python3都支持该语法。

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

4 误解Python范围规范

Python范围解析是基于所谓的LEGB规则。在Python的工作方式中有一些细微之处,让我们看看常见的更高级的Python编程问题:

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

上述问题出现的原因是:当你对作用域中的变量进行赋值时,Python会自动将变量视为该作用域的本地变量,并在任何外部作用域中隐藏任何类似命名的变量。

但在使用列表时,有一个特殊的现象,请看以下代码示例:

>>> lst = [1, 2, 3]
>>> def foo1():
...     lst.append(5)
...
>>> foo1()
>>> lst
[1, 2, 3, 5]
>>> lst = [1, 2, 3]
>>> def foo2():
...     lst += [5]
...
>>> foo2()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in foo2
UnboundLocalError: local variable 'lst' referenced before assignment

咦?为什么foo1良好的运行,但是foo2却报错了??

答案和前面示例问题相同,但无疑更微妙一些。foo1不是分配值到lst,而foo2却是。记住lst += [5]实际是lst = lst + [5]的简写,我们看到foo2正在分配一个值给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]
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
IndexError: list index out of range

在迭代时,从列表或数组中删除项是Python常见的问题。幸运的是Python结合许多优雅的编程范例,如果使用得当可以简化代码。另外一个好处是更简单的代码不太可能被意外删除列表项而导致迭代问题。它完美的工作:

>>> 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 混淆Python如何绑定闭包中的变量

参考以下示例:

>>> 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调用内部函数时,闭包中使用的变量值是后期绑定行为导致的。所以上面的代码中,每当调用任何返回的函数时,在调用i它时,在周围的作用域中查找值,那是循环已经完成,因此i已经分配了它的最终值4。

这个常见问题的解决是有点像黑客的做法:

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

这里利用了默认参数来生成匿名函数,以实现所需的行为,有些人称之为优雅,有些人会认为微免,有些人会讨厌它。但是作为Python的开发人员,无论如何都要理解它。

7 创建循环引用

假设你有两个文件,a.pyb.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足够聪明,不会尝试重新导入它。但是根据每个模块尝试访问另一个模块中定义的函数或变量,你可能会遇到一些问题。

所以回到例子中,当我们导入a.py,它导入b.py有没有问题?因为b.py 不需要从a.py中导入任何变量,这是因为唯一调用的a.f()还是在调用g()时被调用,所以此时a.pyb.py中没有任何内容调用g(),所以一切看起来是美好的。

如果我们尝试导入b.py,看看会发生什么?(前提是没有先导入a.py)

>>> import b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/wen/b.py", line 1, in <module>
    import a
  File "/home/wen/a.py", line 8, in <module>
    print(f())
  File "/home/wen/a.py", line 5, in f
    return b.x
AttributeError: module 'b' has no attribute 'x'
>>>

这就出现问题了。 在导入b.py中,他会尝试导入a.py,而后者又会调用f()尝试访问的内容b.x,但b.x尚未定义,因此出现AttributeError问题。

这里提供一个简单的方案处理这个问题,只需要修改b.py,在g()中导入a.py

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

当我们导入它时,一切都会变得美好:

>>> import b
>>> b.g()
1
1

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

Python的优点之一是它提供了“开箱即用”的丰富的库模块。但是如果你没有意识的避开它,那么在发成自定义模块与Python标准库模块冲突的几率会增大很多。

9 未能解决Python2和Python3之间的差异

考虑一下文件 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()

在Python2上,正常运行:

$ python foo.py 1
key error
1
$ python foo.py 2
value error
2

但是在Python3上:

$ 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的代码:

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

在Python3上运行:

$ python3 foo.py 1
key error
1
$ python3 foo.py 2
value error
2

10 滥用__del__方法

假设你在一个名为的文件中有这个mod.py

import foo

class Bar(object):

    ...

    def __del__(self):
        foo.cleanup(slef.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)

此实现提供了一种干净可靠的方法,可在正常程序终止时调用任何所需的清理功能。显然,foo.cleanup要决定如何处理绑定到名称的对象self.myhandle,但是你明白了。

原文

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 摘要:Python是一门简单易学的编程语言,语法简洁而清晰,并且拥有丰富和强大的类库。在日常开发中,开发者很容犯一...
    山禾家的猫阅读 310评论 0 0
  • 〇、前言 本文共108张图,流量党请慎重! 历时1个半月,我把自己学习Python基础知识的框架详细梳理了一遍。 ...
    Raxxie阅读 18,911评论 17 410
  • 五月,这个季节是属于您的 蔷薇、矢车菊与康乃馨 阳光在花瓣上荡着秋千 一些欢笑从花香里溢出 不,所有的季节都是属于...
    一地月光阅读 318评论 0 3
  • 爱也是一种并非与生俱来的能力,我们将用一生的时间学会如何去爱。 就像《小王子》中的玫瑰和狐狸,我们被教会了如何去爱...
    慕尧VIVIENNNE阅读 426评论 0 8