我个人对陷阱的定义是这样的:看起来有效的代码,但不是你“假设”的那样。如果一段代码直接出错并抛出异常,我认为这不是陷阱。比如Python程序员应该遇到过“UnboundLocalError”,例子:>>>a=1>>>deffunc():...a+=1...printa...>>>func()traceback(mostrecentcallast):File"",line1,inFile"",line2,infuncUnboundLocalError:localvariable'a'referencedbeforeassignment 对于“UnboundLocalError”,还有更高级的版本:importrandomdeffunc(ok):ifok:a=random.random()else:importrandoma=random.randint(1,10)returnfunc(True)#UnboundLocalError:localvariable'random'referencedbeforeassignment可能会让很多python新手感到困惑。但我不认为这是一个陷阱,因为这段代码肯定会报错,而不是默默地跑错了路。不怕真正的小人,就怕伪君子。我认为缺陷就像伪君子。那么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文档给出了一个标准的解决方案:Awayaroundthis是使用None作为默认值,并在函数体中显式测试它>>>defreport(when=None):...ifwhenisNone:...when=time.time()...returnwhen...>>>report()1500113446.746997>>>report()1500113448.552873第二:x+=yvsx=x+y一般来说,两者是等价的,至少看起来是等价的(这也是定义陷阱——看起来一切正常,但不一定正确)。>>>x=1;x+=1;printx2>>>x=1;xx=x+1;printx2>>>x=[1];x+=[2];printx[1,2]>>>x=[1];xx=x+[2];printx[1,2]呃,被光速撞到脸了?>>>x=[1];printid(x);xx=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],[],[],[],[],[],[],[],[],[]]实际编码中遇到的另一个问题,字典。fromkeys也有相同的目的:创建的dict的所有值都指向同一个对象。fromkeys(seq[,value])创建一个新的字典,keysfromseq,valuessettovalue。 第五,访问列表时,修改列表列表(list)在python中使用非常广泛,当然经常在访问列表时添加或删除一些元素。例如,以下函数尝试删除列表中3的倍数的元素:>>>defmodify_lst(lst):...foridx,eleminenumerate(lst):...ifelem%3==0:...dellst[idx]...test,>>>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,ii=i:i*xforiinrange(5)]...七、定义del大部分计算机专业学C、C++的可能先学C++,构造函数,析构函数的概念应该是很熟悉。所以,转用python,自然想知道有没有对应的功能。例如在C++中非常有名的RAII,通过构造和析构来管理资源(如内存和文件描述符)的声明周期。那么如何在python中实现同样的效果呢,就是需要找到一个对象销毁时会调用的函数,所以找到了init和del函数,可能简单的写了两个例子,发现可以确实有效。但事实上,我可能陷入了一个陷阱,这在pythondocumnet中描述:启用选项循环检测器(默认情况下打开)时检测到垃圾循环引用,但只有在没有循环引用时才能清除涉及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()print"letscheck",retrunpythonmain.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。第十:++i—i这个陷阱主要针对C和C++背景的同学。简单来说,++i就是对i取2次正号,-i就是对i取2次负号,运算后i的值不变。第十一:setattrgetattrgetattributePython中有大量的魔法方法(比如xx),其中很多都是和属性访问相关的,比如get、set、delete_、getattr、setattr、delattr、getattribute。前三个与描述符有关。棘手的是getattr与setattr非常不同。简单来说,setattr和getattribute是一一对应的。它们都修改了默认的python属性修改和搜索机制,getattr只有在默认搜索机制找不到属性时才会调用。setattr应该叫setattribute__才合适!第一个否定,gil以GIL结尾,因为gil是Python公认的缺陷!其他语言过来的同学可能看到python用了threading模块,拿到手就用,结果发现效果不对,然后就喷了,搞什么鬼?总结:毫无疑问,python非常好用,而且非常强大。语言。Python非常灵活且高度可定制。同时,也存在一些陷阱,弄清楚这些陷阱才能更好地掌握和使用这样的语言。本文列举了python的一些缺陷,这是一份不完整的清单,欢迎补充。