|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
学习linux,就意味着更快的开发效率,等更多关于软件本身或者说操作系统本身的理解。
关于Python
Python是一门注释性的,面向对象的,并具有静态语义的初级编程言语。它初级的内置数据布局,分离其静态范例和静态绑定的特征,使得它在疾速使用程序开辟(RapidApplicationDevelopment)中很是受接待,同时Python还能作为剧本言语大概胶水言语讲现成的组件大概服务分离起来。Python撑持模块(modules)和包(packages),以是也勉励程序的模块化和代码重用。
关于本文
Python复杂、易学的语法大概会误导一些Python程序员(出格是那些刚打仗这门言语的人们),大概会疏忽某些渺小的地方和这门言语的壮大的地方。
思索到这点,本文列出了“十年夜”乃至是初级的Python程序员都大概犯的,却又不简单发明的渺小毛病。(注重:本文是针对照《Python程序员罕见毛病》略微初级一点读者,关于加倍老手一点的Python程序员,有乐趣能够读一读那篇文章)
罕见毛病1:在函数参数中乱花表达式作为默许值
Python同意给一个函数的某个参数设置默许值以使该参数成为一个可选参数。只管这是这门言语很棒的一个功效,可是这当这个默许值是可变对象(mutable)时,那就有些贫苦了。比方,看上面这个Python函数界说:- >>>deffoo(bar=[]):#bar是可选参数,假如没有指明的话,默许值是[]...bar.append("baz")#可是这行但是有成绩的,走着瞧…...returnbar
复制代码 人们常犯的一个毛病是以为每次挪用这个函数时不给这个可选参数赋值的话,它老是会被付与这个默许表达式的值。比方,在下面的代码中,程序员大概会以为反复挪用函数foo()(不传参数bar给这个函数),这个函数会老是前往‘baz’,由于我们假定以为每次挪用foo()的时分(不传bar),参数bar会被置为[](即,一个空的列表)。
那末我们来看看这么做的时分事实会产生甚么:- >>>foo()["baz"]>>>foo()["baz","baz"]>>>foo()["baz","baz","baz"]
复制代码 嗯?为何每次挪用foo()的时分,这个函数老是在一个已存在的列表前面增加我们的默许值“baz”,而不是每次都创立一个新的列表?
谜底是一个函数参数的默许值,仅仅在该函数界说的时分,被赋值一次。云云,只要当函数foo()第一次被界说的时分,才讲参数bar的默许值初始化到它的默许值(即一个空的列表)。当挪用foo()的时分(不给参数bar),会持续利用bar最早初始化时的谁人列表。
由此,能够有以下的办理举措:- >>>deffoo(bar=None):...ifbarisNone:#大概用ifnotbar:...bar=[]...bar.append("baz")...returnbar...>>>foo()["baz"]>>>foo()["baz"]>>>foo()["baz"]
复制代码
罕见毛病2:不准确的利用类变量
看上面一个例子:- >>>classA(object):...x=1...>>>classB(A):...pass...>>>classC(A):...pass...>>>printA.x,B.x,C.x111
复制代码 看起来没有成绩。- >>>B.x=2>>>printA.x,B.x,C.x121
复制代码 嗯哈,仍是和料想的一样。- >>>A.x=3>>>printA.x,B.x,C.x323
复制代码 我了个往。只是改动了A.x,为啥C.x也变了?
在Python里,类变量一般在外部被当作字典来处置并遵守一般所说的办法剖析按次(MethodResolutionOrder(MRO))。因而在下面的代码中,由于属性x在类C中找不到,因而它会往上往它的基类中查找(在下面的例子中只要A这个类,固然Python是撑持多重承继(multipleinheritance)的)。换句话说,C没有它本人自力于A的属性x。因而对C.x的援用实践上是对A.x的援用。(B.x不是对A.x的援用是由于在第二步里B.x=2将B.x援用到了2这个对象上,倘使没有云云,B.x仍旧是援用到A.x上的。——译者注)
罕见毛病3:在非常处置时毛病的利用参数
假定你有以下的代码:- >>>try:...l=["a","b"]...int(l[2])...exceptValueError,IndexError:#想捕获两个非常...pass...Traceback(mostrecentcalllast):File"<stdin>",line3,in<module>IndexError:listindexoutofrange
复制代码 这里的成绩在于except语句不会像如许往承受一系列的非常。而且,在Python2.x内里,语法exceptException,e是用来将非常和这个可选的参数绑定起来(即这里的e),以用来在前面检察的。因而,在下面的代码中,IndexError非常不会被except语句捕获到;而终极ValueError这个非常被绑定在了一个叫做IndexError的参数上。
在except语句中捕获多个非常的准确做法是将一切想要捕获的非常放在一个元组(tuple)里并作为第一个参数给except语句。而且,为移植性思索,利用as关头字,由于Python2和Python3都撑持如许的语法,比方:- >>>try:...l=["a","b"]...int(l[2])...except(ValueError,IndexError)ase:...pass...>>>
复制代码
罕见毛病4:曲解Python感化域的划定规矩
Python的感化域剖析是基于叫做LEGB(Local(当地),Enclosing(关闭),Global(全局),Built-in(内置))的划定规矩举行操纵的。这看起来很直不雅,对吧?现实上,在Python中这有一些渺小的中央很简单堕落。看这个例子:- >>>x=10>>>deffoo():...x+=1...printx...>>>foo()Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>File"<stdin>",line2,infooUnboundLocalError:localvariablexreferencedbeforeassignment
复制代码 这是怎样回事?
这是由于,在一个感化域内里给一个变量赋值的时分,Python主动以为这个变量是这个感化域的当地变量,并屏障感化域外的同名的变量。
良多时分大概在一个函数里增加一个赋值的语句会让你夙昔原本事情的代码失掉一个UnboundLocalError。(感乐趣的话能够读一读这篇文章。)
在利用列表(lists)的时分,这类情形尤其凸起。看上面这个例子:- >>>lst=[1,2,3]>>>deffoo1():...lst.append(5)#这没有成绩......>>>foo1()>>>lst[1,2,3,5]>>>lst=[1,2,3]>>>deffoo2():...lst+=[5]#...这就有成绩了!...>>>foo2()Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>File"<stdin>",line2,infooUnboundLocalError:localvariablelstreferencedbeforeassignment
复制代码 嗯?为何foo2有成绩,而foo1没有成绩?
谜底和上一个例子一样,可是加倍不容易发觉。foo1并没有给lst赋值,可是foo2实验给lst赋值。注重lst+=[5]只是lst=lst+[5]的简写,由此能够看到我们实验给lst赋值(因而Python假定感化域为当地)。可是,这个要赋给lst的值是基于lst自己的(这里的感化域仍旧是当地),而lst却没有被界说,这就堕落了。
罕见毛病5:在遍历列表的同时又在修正这个列表
上面这个例子中的代码应当对照分明了:- >>>foo()["baz"]>>>foo()["baz","baz"]>>>foo()["baz","baz","baz"]0
复制代码 遍历一个列表大概数组的同时又删除内里的元素,对任何有履历的软件开辟职员来讲这是个很分明的毛病。可是像下面的例子那样分明的毛病,即便有履历的程序员也大概不经意间在加倍庞大的程序中不当心出错。
所幸,Python集成了一些文雅的编程范式,假如利用妥当,能够写出相称简化和精简的代码。一个附加的优点是更复杂的代码更不简单碰到这类“不当心在遍历列表时删失落列表元素”的bug。比方列表推导式(listcomprehensions)就供应了如许的范式。再者,列表推导式在制止如许的成绩上出格有效,接上去这个对下面的代码的从头完成就相称完善:- >>>foo()["baz"]>>>foo()["baz","baz"]>>>foo()["baz","baz","baz"]1
复制代码
罕见毛病6:弄不分明在闭包(closures)中Python是如何绑定变量的
看这个例子:- >>>foo()["baz"]>>>foo()["baz","baz"]>>>foo()["baz","baz","baz"]2
复制代码 希冀失掉上面的输入:- >>>foo()["baz"]>>>foo()["baz","baz"]>>>foo()["baz","baz","baz"]3
复制代码 可是实践上失掉的是:- >>>foo()["baz"]>>>foo()["baz","baz"]>>>foo()["baz","baz","baz"]4
复制代码 不测吧!
这是因为Python的前期绑定(latebinding)机制招致的,这是指在闭包中利用的变量的值,是在内层函数被挪用的时分查找的。因而在下面的代码中,当任一前往函数被挪用的时分,i的值是在它被挪用时的四周感化域中查找(到当时,轮回已停止了,以是i已被付与了它终极的值4)。
办理的举措对照奇妙:- >>>foo()["baz"]>>>foo()["baz","baz"]>>>foo()["baz","baz","baz"]5
复制代码 这下对了!这里使用了默许参数往发生匿名函数以到达希冀的效果。有人会说这很幽美,有人会说这很奇妙,也有人会以为恶感。可是假如你是一位Python程序员,主要的是能了解任何的情形。
罕见毛病7:轮回加载模块
假定你有两个文件,a.py和b.py,在这两个文件中相互加载对方,比方:
在a.py中:- >>>foo()["baz"]>>>foo()["baz","baz"]>>>foo()["baz","baz","baz"]6
复制代码 在b.py中:- >>>foo()["baz"]>>>foo()["baz","baz"]>>>foo()["baz","baz","baz"]7
复制代码 起首,我们试着加载a.py:- >>>foo()["baz"]>>>foo()["baz","baz"]>>>foo()["baz","baz","baz"]8
复制代码 没有成绩。大概让人受惊,究竟有个感到应当是成绩的轮回加载在这儿。
现实上在Python中仅仅是外表上的呈现轮回加载并非甚么成绩。假如一个模块和被加载了,Python不会傻到再往从头加载一遍。可是,当每一个模块都想要相互会见界说在对方里的函数大概变量时,成绩就来了。
让我们再回到之前的例子,当我们加载a.py时,它再加载b.py不会有成绩,由于在加载b.py时,它其实不必要会见a.py的任何工具,而在b.py中独一的援用就是挪用a.f()。可是这个挪用是在函数g()中完成的,而且a.py大概b.py中没有人挪用g(),以是这会儿心境仍是俊丽的。
可是当我们试图加载b.py时(之前没有加载a.py),会产生甚么呢:- >>>foo()["baz"]>>>foo()["baz","baz"]>>>foo()["baz","baz","baz"]9
复制代码 祝贺你,堕落了。这里成绩出在加载b.py的过程当中,Python试图加载a.py,而且在a.py中必要挪用到f(),而函数f()又要会见到b.x,可是这个时分b.x却还没有被界说。这就发生了AttributeError非常。
办理的计划能够做一点渺小的修改。改一下b.py,使得它在g()内里加载a.py:- >>>deffoo(bar=None):...ifbarisNone:#大概用ifnotbar:...bar=[]...bar.append("baz")...returnbar...>>>foo()["baz"]>>>foo()["baz"]>>>foo()["baz"]0
复制代码 这会儿当我们加载b.py的时分,统统安好:- >>>deffoo(bar=None):...ifbarisNone:#大概用ifnotbar:...bar=[]...bar.append("baz")...returnbar...>>>foo()["baz"]>>>foo()["baz"]>>>foo()["baz"]1
复制代码
罕见毛病8:与Python尺度库模块定名抵触
Python的一个优异的中央在于它供应了丰厚的库模块。可是如许的了局是,假如你不下认识的制止,很简单你会碰到你本人的模块的名字与某个随Python附带的尺度库的名字抵触的情形(好比,你的代码中大概有一个叫做email.py的模块,它就会与尺度库中同名的模块抵触)。
这会招致一些很粗拙的成绩,比方当你想加载某个库,这个库必要加载Python尺度库里的某个模块,了局呢,由于你有一个与尺度库里的模块同名的模块,这个包毛病的将你的模块加载了出来,而不是加载Python尺度库里的谁人模块。如许一来就会有贫苦了。
以是在给模块起名字的时分要当心了,得制止与Python尺度库中的模块重名。比拟起你提交一个“Python改善倡议(PythonEnhancementProposal(PEP))”去处上请求改一个尺度库里包的名字,并失掉同意来讲,你把本人的谁人模块从头改个名字要复杂很多。
罕见毛病9:不克不及辨别Python2和Python3
看上面这个文件foo.py:- >>>deffoo(bar=None):...ifbarisNone:#大概用ifnotbar:...bar=[]...bar.append("baz")...returnbar...>>>foo()["baz"]>>>foo()["baz"]>>>foo()["baz"]2
复制代码 在Python2里,运转起来没有成绩:- >>>deffoo(bar=None):...ifbarisNone:#大概用ifnotbar:...bar=[]...bar.append("baz")...returnbar...>>>foo()["baz"]>>>foo()["baz"]>>>foo()["baz"]3
复制代码 可是假如拿到Python3下面玩玩:- >>>deffoo(bar=None):...ifbarisNone:#大概用ifnotbar:...bar=[]...bar.append("baz")...returnbar...>>>foo()["baz"]>>>foo()["baz"]>>>foo()["baz"]4
复制代码 这是怎样回事?“成绩”在于,在Python3里,在except块的感化域之外,非常对象(exceptionobject)是不克不及被会见的。(缘故原由在于,假如不如许的话,Python会在内存的仓库里坚持一个援用链直到Python的渣滓处置将这些援用从内存中扫除失落。更多的手艺细节能够参考这里。)
制止如许的成绩能够如许做:坚持在execpt块感化域之外对非常对象的援用,如许是能够会见的。上面是用这个举措对之前的例子做的修改,如许在Python2和Python3内里都运转都没有成绩。- >>>deffoo(bar=None):...ifbarisNone:#大概用ifnotbar:...bar=[]...bar.append("baz")...returnbar...>>>foo()["baz"]>>>foo()["baz"]>>>foo()["baz"]5
复制代码 在Py3k内里运转:- >>>deffoo(bar=None):...ifbarisNone:#大概用ifnotbar:...bar=[]...bar.append("baz")...returnbar...>>>foo()["baz"]>>>foo()["baz"]>>>foo()["baz"]6
复制代码 耶!
(顺带提一下,我们的“Python雇用指南”里会商了从Python2移植代码到Python3时必要注重的其他主要的分歧的地方。)
罕见毛病10:毛病的利用__del__办法
假定有一个文件mod.py中如许利用:- >>>deffoo(bar=None):...ifbarisNone:#大概用ifnotbar:...bar=[]...bar.append("baz")...returnbar...>>>foo()["baz"]>>>foo()["baz"]>>>foo()["baz"]7
复制代码 然后试图在another_mod.py里如许:- >>>deffoo(bar=None):...ifbarisNone:#大概用ifnotbar:...bar=[]...bar.append("baz")...returnbar...>>>foo()["baz"]>>>foo()["baz"]>>>foo()["baz"]8
复制代码 那末你会失掉一个恶心的AttributeError非常。
为啥呢?这是由于(参考这里),当注释器封闭时,模块一切的全局变量会被置为空(None)。了局便如上例所示,当__del__被挪用时,名字foo已被置为空了。
利用atexit.register()能够办理这个成绩。云云,当你的程序停止的时分(加入的时分),你的注册的处置程序会在注释器封闭之前处置。
如许了解的话,对下面的mod.py能够做以下的修正:- >>>deffoo(bar=None):...ifbarisNone:#大概用ifnotbar:...bar=[]...bar.append("baz")...returnbar...>>>foo()["baz"]>>>foo()["baz"]>>>foo()["baz"]9
复制代码 如许的完成体例为在程序一般停止时挪用扫除功效供应了一种洁净牢靠的举措。明显,必要foo.cleanup决意怎样处置绑定在self.myhandle上的对象,但你晓得怎样做的。
在这里你会学到更多的知识,学习linux,更要学习一种geek的精神,python之禅中也说过:以总结分享为荣,以跪求其解为耻; |
|