当前位置: 首页 > 科技观察

Python程序员常犯的10个错误

时间:2023-03-13 23:43:19 科技观察

关于PythonPython是一种解释型、面向对象、具有动态语义的高级编程语言。它内置高级数据结构,结合动态类型和动态绑定的优点,这使得它在快速应用程序开发中非常有吸引力,可以用作脚本或胶水语言来连接现有的组件或服务。Python支持模块和包,这鼓励程序模块化和代码重用。关于本文Python易于学习的语法可能会导致Python开发人员(尤其是那些刚接触编程的开发人员)忽略它的一些微妙之处并低估该语言的功能。考虑到这一点,本文提供了一个“Top10”错误列表,即使是高级Python开发人员有时也很难捕捉到这些错误。常见错误#1:误用表达式作为函数参数的默认值Python允许函数参数使用默认的可选值。尽管这是该语言的一个重要特性,但它可能会导致与某些可变默认值的混淆。例如,看看这个Python函数的定义:>>>deffoo(bar=[]):#barisoptionalanddefaultsto[]ifnotspecified...bar.append("baz")#butthislinecouldbeproblematic,aswe'llsee......returnbar一个常见的错误是认为每次调用函数时可选参数都会被设置为默认指定值而不提供它们。例如,在上面的代码中,人们可能期望重复调用foo()(即没有明确指定bar参数)将始终返回“baz”,因为每次调用foo()都假设(没有设置bar参数)bar设置为[](即空列表)。但是让我们看看当我们这样做时究竟发生了什么:>>>foo()["baz"]>>>foo()["baz","baz"]>>>foo()["baz","baz","baz"]是吗?为什么每次调用foo()时都会将默认值“baz”附加到现有列表而不是创建一个新列表?答案是函数参数的默认值只计算一次——在函数定义时。因此,bar参数被初始化为其默认值(即一个空列表),即当foo()***被定义时,但在调用foo()时将继续使用bar(即当没有指定bar参数时))已经初始化的参数。这是一个常见的解决方法:>>>deffoo(bar=None):...ifbarisNone:#orifnotbar:...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中,类变量在内部被视为字典,它遵循经常被引用的方法解析顺序(MRO)。所以在上面的代码中,由于没有找到C类中的x属性,所以会去查找它的基类(虽然Python支持多重继承,但是上面的例子中只有A)。也就是说,类C没有自己的x属性,它是独立于A的。因此,C.x实际上是对A.x的引用。常见错误#3:为except指定错误的参数假设您有以下代码:>>>try:...l=["a","b"]...int(l[2])...exceptValueError,IndexError:#Tocatchbothexceptions,对吧?...pass...Traceback(mostrecentcalllast):File"",line3,inIndexError:listindexoutofrange这里的问题是except语句不接受指定的异常列表。相反,在Python2.x中,使用语法exceptException,e将异常对象绑定到第二个可选参数(本例中为e)以供以后使用。所以,在上面的例子中,异常IndexError并没有被except语句捕获,而是在它绑定到一个名为IndexError的参数时抛出。在except语句中捕获多个异常的正确方法是将第一个参数指定为包含所有要捕获的异常的元组。而且,为了代码的可移植性,使用as关键字,因为Python2和Python3都支持这种语法:>>>try:...l=["a","b"]...int(l[2])...except(ValueError,IndexError)ase:...pass...>>>常见错误#4:不了解Python的作用域Python是基于LEGB进行解析的,LEGB是Local的缩写,封闭的、全局的、内置的。看起来像是“知文义”吧?其实Python还是有一些需要注意的,先看下面这段代码:>>>x=10>>>deffoo():...x+=1...printx...>>>foo()Traceback(mostrecentcalllast):File"",line1,inFile"",line2,infoUnboundLocalError:localvariable'x'referencedbeforeassignment这里有什么问题?出现上述问题是因为当你给作用域内的变量赋值时,Python会自动将其视为当前作用域的局部变量,从而隐藏外层作用域的同名变量。许多人在将赋值语句添加到之前运行的函数体中某处后得到UnboundLocalError时感到惊讶。(您可以在此处阅读更多相关信息。)尤其是当开发人员使用列表时,这个问题更为常见。看下面的例子:>>>lst=[1,2,3]>>>deffoo1():...lst.append(5)#Noproblem......>>>foo1()>>>lst[1,2,3,5]>>>lst=[1,2,3]>>>deffoo2():...lst+=[5]#...但是这里有个问题!...>>>foo2()Traceback(mostrecentcalllast):File"",line1,inFile"",line2,infooUnboundLocalError:localvariable'lst'referencedbeforeassignment是吗?为什么foo2报错,foo1却没事?原因与前面的示例相同,但更难以捉摸。foo1没有分配给lst,但是foo2分配了。你知道,lst+=[5]是lst=lst+[5]的缩写,我们试图分配给lst(Python将其视为局部变量)。此外,我们对lst的赋值是基于lst本身(它再次被Python视为局部变量),但它尚未定义。因此错误!常见错误#5:在迭代时修改列表以下代码中的问题应该相当明显:>>>odd=lambdax:bool(x%2)>>>numbers=[nforninrange(10)]>>>foriinrange(len(numbers)):...ifodd(numbers[i]):...delnumbers[i]#BAD:Deletingitemfromalistwhileiteratingoverit...Traceback(mostrecentcallast):File"",line2,in索引错误:listindexoutofrange在迭代时,从列表或数组中删除元素对于任何有经验的开发人员来说都是众所周知的错误。虽然上面的示例很明显,但许多高级开发人员在更复杂的代码中无意中这样做了。幸运的是,Python包含了大量简洁优雅的编程范式,如果使用得当,可以极大地简化和提炼代码。这样做的好处是可以得到更加精简精简的代码,也可以更好的避免程序在迭代时修改一个列表(List)的bug。一种这样的范例是列表理解。此外,递归列表(列表理解)对于这个问题特别有用。通过改变上面的实现,得到一个完美的代码:>>>odd=lambdax:bool(x%2)>>>numbers=[nforninrange(10)]>>>numbers[:]=[nforninnumbersifnotodd(n)]#ahh,thebeautyofitall>>>numbers[0,2,4,6,8]常见错误#6:不懂Python如何在包中绑定变量,请参见以下示例:>>>defcreate_multipliers():...return[lambdax:i*xforiinrange(5)]>>>formmultiplierincreate_multipliers():...printmultiplier(2)..您可能希望获得以下输出:02468但实际结果是:88888惊喜!发生这种情况是因为Python中的“后期绑定”行为——变量中使用的闭包仅在函数被调用时才被赋值。所以,在上面的代码中,每当返回的函数被调用时,Python都会在函数被调用时的作用域中查找i对应的值(此时循环已经结束,所以i被赋值为Finalvalue-4).解决方案有点老套:>>>defcreate_multipliers():...return[lambdax,i=i:i*xforiinrange(5)]...>>>formmultiplierincreate_multipliers():...printmultiplier(2)。..02468这里我们利用默认参数生成一个匿名函数来达到我们想要的结果。有人说它巧妙,有人说它不可理解,有人讨厌它。但是,如果您是Python开发人员,那么理解这种行为很重要。#p#常见错误#7:创建循环依赖模块假设您有两个文件a.py和b.py,它们像这样相互引用:a.py:importbdeff():returnb。xprintf()b.py:importax=1defg():printa.f()首先,让我们尝试导入a.py:>>>importa1工作正常。这对你来说可能很奇怪。毕竟,我们确实在这里引入了一个循环依赖模块,我们认为这会有问题,不是吗?答案是在Python中,只要引入一个循环依赖的模块是没有问题的。如果一个模块已经被导入,Python不会重新导入它。但是,根据每个模块访问其他模块中的函数和变量的位置,您可能会遇到问题。所以,回到我们的例子,当我们导入a.py时,导入b.py不会有任何问题,因为导入时,b.py不需要在a.py中定义任何东西。b.py中唯一引用a.py中的内容的是对a.f()的调用。但是这个调用发生在g()中,而a.py和b.py都不调用g()。所以它工作正常。但是如果我们尝试导入b.py会发生什么?(在此之前不要引入a.py),如下:>>>importbTraceback(mostrecentcalllast):File"",line1,inFile"b.py",line1,inimportaFile"a.py",line6,inprintf()File"a.py",line4,infreturnb.xAttributeError:'module'objecthasnoattribute'x'啊哦。有一个问题!这里的问题是,在引入b.py的过程中,Python试图导入a.py,但是a.py调用了f(),而f()试图访问b.x。但是此时b.x还没有定义。所以会出现AttributeError异常。至少,解决这个问题很简单,修改b.py,在g()中导入a.py即可:x=1defg():importa#willonlyimportawheng()iscalledprinta.f()现在,当我们再次导入b时,就没有问题了:>>>importb>>>b.g()1#Printedfirsttimesincemodule'a'calls'printf()'attheend1#Printedasecondtime,thisoneisourcallto'g'CommonMistake#8:与Python标准库中的模块命名冲突Python的一大优点是它拥有丰富的模块,我们可以“开箱即用”。但是,如果你不自觉地注意,你写的模块很容易和Python标准库的模块发生命名冲突(比如,你可能有一个名为email.py的模块,但这会和一个模块冲突在标准库中同名)。这会导致奇怪的问题,比如你导入了另一个模块,但是这个模块导入了Python标准库中的一个模块,因为你定义了一个同名的模块,会导致模块被误导入,而不是stdlib中的模块。这就是问题所在。因此,我们必须注意这个问题,避免使用与Python标准库中相同的模块名。更改包中的模块名称比通过Python增强建议(PEP)向Python提议更改标准库的模块名称要容易得多。常见错误#9:未能解决Python2和Python3之间的差异,请参阅此文件foo.py:importsysdefbar(i):ifi==1:raiseKeyError(1)ifi==2:raiseValueError(2)defbad():e=Nonetry:bar(int(sys.argv[1]))exceptKeyErrorase:print('keyerror')exceptValueErrorase:print('valueerror')print(e)bad()在Python2中工作正常:$pythonfoo.py1keyerror1$pythonfoo.py2valueerror2但现在让我们在Python3中运行它:$python3foo.py1keyerrorTraceback(mostrecentcallast):File"foo.py",line19,inbad()File"foo.py",line17,inbadprint(e)UnboundLocalError:localvariable'e'referencedbeforeassignment怎么了?“问题”是,在Python3中,异常对象在except块之外是不可见的。(这样做的原因是它会在内存中保留对堆栈帧的引用直到垃圾收集器运行并从内存中清除引用。有关更多技术细节,请参见此处)。一种解决方案是在except块的外部范围内定义对异常对象的引用,以便于访问。下面的例子使用了这个方法,所以***代码在Python2和Python3中都可以正常工作。importsysdefbar(i):ifi==1:raiseKeyError(1)ifi==2:raiseValueError(2)defgood():exception=Nonetry:bar(int(sys.argv[1]))exceptKeyErrorase:exception=e打印('keyerror')exceptValueErrorase:exception=eprint('valueerror')print(exception)good()在Py3k中运行:$python3foo.py1keyerror1$python3foo.py2valueerror2OK!(顺便说一下,我们的Python招聘指南讨论了将代码从Python2迁移到Python3时需要了解的其他一些重要差异。)常见错误#10:滥用__del__方法假设您有一个名为mod.py的文件:importfooclassBar(object):...def__del__(self):foo.cleanup(self.myhandle)并且有一个名为another_mod.py的文件:importmodmybar=mod.Bar()你会得到一个AttributeError异常。为什么?因为,正如这里所说,当解释器退出时,模块中的全局变量都被设置为None。因此,在上面的示例中,调用__del__时foo已经设置为None。解决方案是改用atexit.register()。这样,当你的程序执行完毕(意思是正常退出)时,你注册的处理程序会在解释器退出前执行。知道了这一点,我们就可以将上面mod.py的代码修改为如下:.myhandle)这个实现提供了一种简洁可靠的方法来在程序退出之前进行一些清理。显然,由foo.cleanup决定如何处理绑定到self.myhandle的对象,但这就是你想要的。总之,Python是一种强大而灵活的语言,具有许多机制和语言约定,可以显着提高您的工作效率。与任何其他语言或软件一样,对其功能的了解有限很可能会阻碍你而不是从中受益。俗话说,“知足常危”(译者注:意思是认为自己知道的足够多,可以做某事,但实际上并没有)。熟悉Python的一些关键细节,如本文中提到的那些(但不限于这些),可以帮助我们更好地使用这门语言并避免一些常见的陷阱。您可以查看“Python面试官指南”以获取有关如何判断开发人员是否是Python专家的一些建议。我们希望您在本文中找到有用的东西,我们希望您能得到您的反馈。英文原文:Top10MistakesthatPythonProgrammersMake翻译链接:http://www.oschina.net/translate/top-10-mistakes-that-python-programmers-make