十个Python措施员易犯的错误
不管是在进修照往事情进程中,人城市出错。固然Python的语法简朴、机动,但也一样存在一些不小的坑,一不小心,初学者和资深Python措施员都有大概会栽跟头。本文为各人分享了10大常见错误,需要的伴侣可以参考下
常见错误1:错误地将表达式作为函数的默认参数
在Python中,我们可觉得函数的某个参数配置默认值,使该参数成为可选参数。固然这是一个很好的语言特性,可是当默认值是可变范例时,也会导致一些令人狐疑的环境。我们来看看下面这个Python函数界说:
>>> def foo(bar=[]): # bar是可选参数,假如没有提供bar的值,则默认为[], ... bar.append("baz") # 可是稍后我们会看到这行代码会呈现问题。 ... return bar
Python措施员常犯的一个错误,就是想虽然地认为:在每次挪用函数时,假如没有为可选参数传入值,那么这个可选参数就会被配置为指定的默认值。在上面的代码中,你们大概以为反复挪用foo()函数应该会一直返回'baz',因为你们默认每次foo()函数执行时(没有指定bar变量的值),bar变量都被配置为[](也就是,一个新的空列表)。
可是,实际运行功效却是这样的:
>>> foo() ["baz"] >>> foo() ["baz", "baz"] >>> foo() ["baz", "baz", "baz"]
很奇怪吧?为什么每次挪用foo()函数时,城市把"baz"这个默认值添加到已有的列表中,而不是从头建设一个新的空列表呢?
谜底就是,可选参数默认值的配置在Python中只会被执行一次,也就是界说该函数的时候。因此,只有当foo()函数被界说时,bar参数才会被初始化为默认值(也就是,一个空列表),可是之后每次foo()函数被挪用时,城市继承利用bar参数原先初始化生成的谁人列表。
虽然,一个常见的办理步伐就是:
>>> 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
在Python语言中,类变量是以字典的形式举办处理惩罚的,而且遵循要领理会顺序(Method Resolution Order,MRO)。因此,在上面的代码中,由于类C中并没有x这个属性,表明器将会查找它的基类(base class,尽量Python支持多重担任,可是在这个例子中,C的基类只有A)。换句话说,C并不没有独立于A、真正属于本身的x属性。所以,引用C.x实际上就是引用了A.x。假如没有处理惩罚好这里的干系,就会导致示例中呈现的这个问题。
常见错误3:错误地指定异常代码块(exception block)的参数
请看下面这段代码:
>>> try: ... l = ["a", "b"] ... int(l[2]) ... except ValueError, IndexError: # To catch both exceptions, right? ... pass ... Traceback (most recent call last): File "<stdin>"</stdin>, line 3, in <module> IndexError: list index out of range
这段代码的问题在于,except语句并不支持以这种方法指定异常。在Python 2.x中,需要利用变量e将异常绑定至可选的第二个参数中,才气进一步查察异常的环境。因此,在上述代码中,except语句并没有捕捉IndexError异常;而是将呈现的异常绑定到了一个名为IndexError的参数中。
要想在except语句中正确地捕捉多个异常,则应将第一个参数指定为元组,然后在元组中写下但愿捕捉的异常范例。别的,为了提高可移植性,请利用as要害词,Python 2和Python 3均支持这种用法。
>>> try: ... l = ["a", "b"] ... int(l[2]) ... except (ValueError, IndexError) as e: ... pass ... >>>
常见错误4:错误领略Python中的变量名理会
#p#分页标题#e#
Python中的变量名理会遵循所谓的LEGB原则,也就是“L:当地浸染域;E:上一层布局中def或lambda的当地浸染域;G:全局浸染域;B:内置浸染域”(Local,Enclosing,Global,Builtin),按顺序查找。看上去是不是很简朴?不外,事实上这个原则的生效方法照旧有着一些非凡之处。说到这点,我们就不得不提下面这个常见的Python编程错误。请看下面的代码:
>>> x = 10 >>> def foo(): ... x += 1 ... print x ... >>> foo() Traceback (most recent call last): File "<stdin>"</stdin>, line 1, in <module> File "<stdin>"</stdin>, line 2, in foo UnboundLocalError: local variable 'x' referenced before assignment
出了什么问题?
上述错误的呈现,是因为当你在某个浸染域内为变量赋值时,该变量被Python表明器自动视作该浸染域的当地变量,并会代替任何上一层浸染域中沟通名称的变量。
正是因为这样,才会呈现一开始好好的代码,在某个函数内部添加了一个赋值语句之后却呈现了UnboundLocalError,难怪会让很多人受惊。
在利用列表时,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>"</stdin>, line 1, in <module> File "<stdin>"</stdin>, line 2, in foo 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] # BAD: Deleting item from a list while iterating over it ... Traceback (most recent call last): File "<stdin>"</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在闭包中如何绑定变量
#p#分页标题#e#
请看下面这段代码:
>>> def create_multipliers(): ... return [lambda x : i * x for i in range(5)] >>> for multiplier in create_multipliers(): ... print multiplier(2) ...
你大概以为输出功效应该是这样的:
可是,实际的输出功效却是:
吓了一跳吧!
这个功效的呈现,主要是因为Python中的迟绑定(late binding )机制,即闭包中变量的值只有在内部函数被挪用时才会举办查询。因此,在上面的代码中,每次create_multipliers()所返回的函数被挪用时,城市在四周的浸染域中查询变量i的值(而到当时,轮回已经竣事,所以变量i最后被赋予的值为4)。
要办理这个常见Python问题的要领中,需要利用一些hack能力:
>>> 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
请留意!我们在这里操作了默认参数来实现这个lambda匿名函数。有人大概认为这样做很优雅,有人会以为很巧妙,尚有人会嗤之以鼻。可是,假如你是一名Python措施员,不管奈何你都应该要相识这种办理要领。
常见错误7:模块之间呈现轮回依赖(circular dependencies)
假设你有两个文件,别离是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模块:
代码运行正常。也许这出乎了你的料想。究竟,我们这里存在轮回引用这个问题,想必应该是会呈现问题的,莫非不是吗?
谜底是,仅仅存在轮回引用的环境自己并不会导致问题。假如一个模块已经被引用了,Python可以做到不再次举办引用。可是假如每个模块试图会见其他模块界说的函数或变量的机缘差池,那么你就很大概陷入逆境。
那么回到我们的示例,当我们导入a.py模块时,它在引用b.py模块时是不会呈现问题的,因为b.py模块在被引用时,并不需要会见在a.py模块中界说的任何变量或函数。b.py模块中对a模块独一的引用,就是挪用了a模块的foo()函数。可是谁人函数挪用产生在g()函数傍边,而a.py或b.py模块中都没有挪用g()函数。所以,不会呈现问题。
可是,假如我们试着导入b.py模块呢(即之前没有引用a.py模块的前提下):
>>> import b Traceback (most recent call last): File "<stdin>"</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模块,而a.py模块接着又要挪用foo()函数,这个foo()函数接着又试图去会见b.x变量。可是这个时候,b.x变量还没有被界说,所以才呈现了AttributeError异常。
办理这个问题有一种很是简朴的要领,就是简朴地修改下b.py模块,在g()函数内部才引用a.py:
x = 1 def g(): import a # This will be evaluated only when g() is called print a.f()
此刻我们再导入b.py模块的话,就不会呈现任何问题了:
>>> 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尺度库模块名斗嘴
#p#分页标题#e#
Python语言的一大优势,就是其自己自带的强大尺度库。可是,正因为如此,假如你不去决心留意的话,你也是有大概为本身的模块取一个和Python自带尺度库模块沟通的名字(譬喻,假如你的代码中有一个模块叫email.py,那么这就会与Python尺度库中同名的模块相斗嘴。)
这很大概会给你带来难缠的问题。举个例子,在导入模块A的时候,如果该模块A试图引用Python尺度库中的模块B,但却因为你已经有了一个同名模块B,模块A会错误地引用你本身代码中的模块B,而不是Python尺度库中的模块B。这也是导致一些严重错误的原因。
因此,Python措施员要分外留意,制止利用与Python尺度库模块沟通的名称。究竟,修改本身模块的名称比提出PEP提议修改上游模块名称且让提议通过,要来得容易的多。
常见错误9:未能办理Python 2与Python 3之间的差别
假设有下面这段代码:
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
这到底是怎么回事?这里的“问题”是,在Python 3中,异常工具在except代码块浸染域之外是无法会见的。(这么设计的原因在于,假如不这样的话,仓库帧中就会一直保存它的引用轮回,直到垃圾接纳器运行,将引用从内存中排除。)
制止这个问题的一种要领,就是在except代码块的浸染域之外,维持一个对异常工具的引用(reference),这样异常工具就可以会见了。下面这段代码就利用了这种要领,因此在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()
在Python 3下运行代码:
$ 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(self.myhandle)
之后,你在another_mod.py文件中举办如下操纵:
import mod mybar = mod.Bar()
假如你运行another_mod.py模块的话,将会呈现AttributeError异常。
为什么?因为当表明器竣事运行的时候,该模块的全局变量城市被配置为None。因此,在上述示例中,当__del__要领被挪用之前,foo已经被配置成了None。
要想办理这个有点棘手的Python编程问题,个中一个步伐就是利用atexit.register()要领。这样的话,当你的措施执行完成之后(即正常退出措施的环境下),你所指定的处理惩罚措施就会在表明器封锁之前运行。
#p#分页标题#e#
应用了上面这种要领,修改后的mod.py文件大概会是这样子的:
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所绑定的工具。
综述
Python是一门强大而又机动的编程语言,提供的很多编程机制和范式可以极大地提高事情效率。可是与任何软件东西或语言一样,假如对该语言的本领领略有限或无法浏览,那么有时候本身反而会被阻碍,而不是受益了。正如一句谚语所说,“自觉得知道够多,但实则会给本身或别人带来危险。
不绝地熟悉Python语言的一些细微之处,尤其是本文中提到的10大常见错误,将会辅佐你有效地利用这门语言,同时也能制止犯一些较量常见的错误。