程序员和老司机不得不犯的Python陷阱和缺陷清单抛出异常,我觉得不是陷阱。比如Python程序员应该遇到过“UnboundLocalError”,例子:>>>a=1>>>deffunc():...a+=1...printa...>>>func()Traceback(mostrecentcallast):File"",line1,inFile"",line2,infuncUnboundLocalError:localvariable'a'referencedbeforeassignmentfor"UnboundLocalError",还有一个更高级的版本:importrandomdeffunc(ok):ifok:a=random.random()else:importrandom=random.randint(1,10)returnfunc(True)#UnboundLocalError:localvariable'random'referencedbeforeassignment可能对很多python新手有用对于我来说,这个Error让人迷惑。但是我觉得不是陷阱,因为这段代码肯定会报错,而不是默默的跑错路。不怕真正的小人,就怕伪君子。我认为缺陷是它像一个伪君子。那么Python中真正的陷阱是什么?***:使用可变对象作为默认参数的想法可能是最广为人知的。与许多其他语言一样,Python提供了默认参数。默认参数确实是个好东西。可以让函数调用者忽略一些细节(比如GUI编程,Tkinter,QT),对lambda表达式也很有用。但是如果可变对象被用作默认参数,那么事情就没那么愉快了。>>>deff(lst=[]):...lst.append(1)...returnlst...>>>f()[1]>>>f()[1,1]惊喜与否?!原因是python中一切皆对象,函数也不例外。默认参数只是函数的一个属性。定义函数时已经评估了默认参数。执行函数定义时会评估默认参数值。stackoverflow上有一个更合适的示例来说明在执行函数定义时评估默认参数。>>>importtime>>>defreport(when=time.time()):...returnwhen...>>>report()1500113234.487932>>>report()1500113234.487932python文档给出了一个标准的解决方案:AwayaroundthisistousNoneasthedefault,并且明确testforitinthebodyofthefunction>>>defreport(when=None):...ifwhenisNone:...when=time.time()...returnwhen...>>>report()1500113446.746997>>>report()1500113448.552873Second:x+=yvsx=x+y一般来说,两者是等价的,至少它们看起来是等价的(这是陷阱的定义——看起来都不错,但不一定正确)。>>>x=1;x+=1;printx2>>>x=1;x=x+1;printx2>>>x=[1];x+=[2];printx[1,2]>>>x=[1];x=x+[2];printx[1,2]呃,被光速击中了?>>>x=[1];printid(x);x=x+[2];printid(x)43571328004357132728>>>x=[1];printid(x);x+=[2];printid(x)43571328004357132800前者x指向新对象,后者x在原对象中修改,的当然,那种效果是否正确取决于应用场景。至少,你要知道两者有时是不同的。三、神奇的括号——()括号在各种编程语言中被广泛使用。在python中,括号也可以表示元组(tuple)数据类型,一个元组是一个不可变的序列。>>>a=(1,2)>>>type(a)>>>type(())但是如果只有一个元素怎么办>>>a=(1)>>>type(a)魔法不魔法,如果要表示只有一个元素的元组,正确的姿势是:>>>a=(1,)>>>type(a)第四:生成一个列表,其元素是列表。这有点像二维数组。当然,也可以生成一个元素为字典的列表。更一般地,生成一个其元素是变量对象的序列是非常简单的。那么:>>>a=[[]]*10>>>a[[],[],[],[],[],[],[],[],[],[]]>>>a[0].append(10)>>>a[0][10]看起来很简单,但是>>>a[1][10]>>>a[[10],[10],[10],[10],[10],[10],[10],[10],[10],[10]]我猜,这应该不是你期望的结果。原因是,或者因为python中list是一个变量对象,上面的写法大家指向同一个变量对象,正确的姿势>>>a=[[]for_inxrange(10)]>>>a[0].append(10)>>>a[[10],[],[],[],[],[],[],[],[],[]]五、访问列表时,修改列表列表(list)在python中被广泛使用,当然访问列表时也经常会添加或删除一些元素。例如,以下函数尝试删除列表中3的倍数的元素:>>>defmodify_lst(lst):...foridx,eleminenumerate(lst):...ifelem%3==0:...dellst[idx]测试,>>>lst=[1,2,3,4,5,6]>>>modify_lst(lst)>>>lst[1,2,4,5]好像没什么问题,但这只是祝你好运>>>lst=[1,2,3,6,5,4]>>>modify_lst(lst)>>>lst[1,2,6,5,4]在上面的例子中,6这个元素没有被删除。如果在modify_lst函数中打印idx,item可以找到蛛丝马迹:lst变短了,但idx变大了,所以在上面的错误例子中,删除3后,6变成了lst的第二个元素(从0开始)。在C++中,如果在遍历容器时使用迭代器删除元素,也会出现同样的问题。如果逻辑比较简单,用列表推导就好了。注意第六,闭包和lambda也是一个老套的例子,其他语言也有类似的情况。我们先看一个例子:>>>defcreate_multipliers():...return[lambdax:i*xforiinrange(5)]...>>>formmultiplierincreate_multipliers():...printmultiplier(2)...returnofcreate_multipliersfunction值是一个列表,其中的每个元素都是一个函数——一个将输入参数x乘以一个倍数i的函数。预期的结果是0,2,4,6,8。但是结果是5个8,这并不奇怪。由于出现这个陷阱时经常使用lambda,所以可能会认为是lambda的问题,但是lambda表示不愿意背锅。问题的本质在于python中的属性查找规则,LEGB(local,enclosing,global,bulitin)。上面的例子中,i是在闭包范围内(enclosing),Python的闭包就是后期绑定。这意味着在调用内部函数时查询闭包中使用的变量的值。解决办法也很简单,就是把闭包作用域改成局部作用域。>>>defcreate_multipliers():...return[lambdax,i=i:i*xforiinrange(5)]...七、定义__del__大多数计算机专业的人可能先学C和C++,构造函数和析构函数的概念应该是很熟悉。所以,转用python,自然想知道有没有对应的功能。例如在C++中非常有名的RAII,通过构造和析构来管理资源(如内存和文件描述符)的声明周期。在python中如何实现同样的效果,就是需要找到一个对象销毁时会调用的函数,所以找到了__init__,__del__函数,可能简单写了两个例子,发现确实可以工作。但实际上,它可能陷入了一个陷阱,这在pythondocumnet:Circularreferencewhichregarbagaredetectedwhentheoptioncycledetectorisenabled(it’sonbydefault)中有描述,但如果有Python级别的__del__()方法涉及,则只能被清除。简单的说,如果循环引用中的对象定义了__del__,那么pythonGC就无法回收,因此存在内存泄漏的风险。八、不同姿势导入同一个模块示例。稍微修改一下stackoverflow的例子。假设有一个名为mypackage的包,其中包含三个python文件:mymodule.py、main.py、__init__.py。mymodule.py的代码如下:l=[]classA(object):passmain.py代码如下:defadd(x):frommypackageimportmymodulemymodule.l.append(x)print"updatedlist",mymodule.l,id(mymodule)defget():importmymoduleprint'moduleinget',id(mymodule)returnmymodule.lif__name__=='__main__':importsyssys.path.append('../')add(1)ret=get()打印“letscheck”,重新运行pythonmain。py,结果如下:updatedlist[1]4406700752moduleinget4406700920letscheck[]从运行结果可以看出add和get函数中导入的mymodule不是同一个模块,ID不同。当然在python2.7.10中需要main.py的第13行才能有这样的效果。你可能会问,谁会写第13行这样的代码?其实在很多项目中,为了导入方便,会在sys.path中加入一堆路径。那么在项目中,非常有必要让大家约定一个导入方式。九、python升级python3.x是不向下兼容的,所以如果从2.x升级到3.x,就要小心了。Point:在python2.7中,range的返回值是一个列表;在python3.x中,返回一个范围对象。map()、filter()、dict.items()在python2.7中返回列表,但在3.x中返回迭代器。当然,大多数迭代器是更好的选择,更pythonic,但它们也有只能遍历一次的缺点。在instagram的分享中,也提到有一个由此导致的作弊bug。第十,gil以GIL结尾,因为gil是Python公认的缺陷!其他语言的同学可能看到python用了threading模块,拿到手就用,结果发现效果不对,然后就喷了,搞什么鬼。总结:毫无疑问,python是一门非常容易上手而且非常强大的语言。Python非常灵活且高度可定制。同时,也存在一些陷阱,弄清楚这些陷阱才能更好地掌握和使用这样的语言。本文列举了python的一些缺陷,这是一份不完整的清单,欢迎补充。