Python作为一门简洁且容易上手的语言,正在受到越来越多人的喜爱。但如果你对其中的一些细节不甚了解,就有可能掉入它的“坑”里。本文将介绍学习Python过程中遇到的一些问题,接下来看看Python里一些常见的坑。
01
三元表达式
乍一看,按照根深蒂固的四则运算的思维,加号之前是一部分,加号之后为另一部分,结果貌似等于10。为什么打印出来的结果跟我们预想的大相径庭呢?很显然,Python解释器在遇到三元表达式时,默认把if之前的(10+4)作为三元表达式的前面部分了。
02
嵌套列表的创建
要创建一个嵌套的列表,我们可能会选择这样的方式:
目前看起来一切正常,我们得到了一个包含3个list的嵌套list。接下来往第一个list中添加一个元素:
奇怪的事情发生了,我们只想给第一元素增加元素,结果三个list都增加了一个元素。这是因为[[]]*3并不是创建了三个不同list,而是创建了三个指向同一个list的对象,所以,当我们操作第一个元素时,其他两个元素内容也会发生变化。下面的代码可以证实这一点:
正确的做法如下:
03
try...finally + return
看下面这段代码,可以试想一下print语句打印的顺序:
是不是很多小伙伴看到return就对print的顺序感到不知所措了,下图是最终的结果:
我们先来看一段Python官网对于finally的解释:
翻译成中文就是try块中包含break、continue或者return语句的,在离开try块之前,finally中的语句也会被执行。所以在上面的例子中,try块return之前,会执行finally中的语句,最后再执行try中的return语句返回结果。看到这里小伙伴们都豁然开朗了吧。
04
参数默认值
当我们把函数的参数默认值设置为列表时,会发生什么?
出现以上情况的原因是:默认值在定义函数时计算(通常在加载模块时),因此默认值变成了函数对象的属性。具体来说,函数的参数默认值保存在函数的defaults属性中,指向了同一个列表。因此,如果默认值是可变对象,而且修改了它的值,那么后续的函数调用都会受到影响。正确的做法是设置该参数默认为None。
05
lambda自由参数之坑
先来看这样一段代码:
结果并不是0,2,4,6,8,而是8,8,8,8,8。不少人可能会觉得匪夷所思,不着急,先试着用dis库分析字节码。
没有得到想要的结果,只能看到参数i和x,参数i的具体值无法获取。这也就是说lambda函数在定义的时候只知道有一个i,而他的值并不明确,之后通过计算获取i的值。到这里很容易联想到闭包,因为i引用了“for i in range(5)”这个表达式中的值。先复习一下“闭包”的定义:闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。接下来验证一下,我们通过f.code.co_freevars来获取自由变量的名称,通过f.closure[0].cell_contents得到自由变量的值:
果不其然,自由变量i最终的值都是4,这也就解释了最开始的结果。如果还不明白可以看下面这段代码。
Python程序从上到下执行,同时它也是一门动态型的语言,举个例子,定义一个类之后,你可以动态的给它增加方法。同样,上面这个例子中,程序执行到最后i的值为5,所以lambda表达式中i为5,最终的结果为:[10, 10,10, 10, 10, 10]。
要解决上述出现的问题,就要把闭包作用域变为局部作用域:
a = [lambda x, i=i: i*x for i in range(5)]。这行代码等效于下面这种写法:
“纸上得来终觉浅,绝知此事要躬行”。
06
含单个元素的元组
上图有两行新建元组的代码,但只有第二种写法是正确的。因为在唯一的元素之后不加逗号,小括号对于Python解释器是无效的。
07
对象销毁顺序
创建一个类OBJ:
创建两个OBJ示例,使用is判断是否为同一对象:
接下来同样创建两个对象,使用id来判断。
调用id函数, Python 创建一个OBJ类的实例,并使用id函数获得内存地址后,销毁内存丢弃这个对象。当连续两次进行此操作, Python会将相同的内存地址分配给第二个对象,所以两个对象的id值是相同的。但是is行为却与之不同,通过打印顺序就可以看到。
08
了解执行时机
请看下面这个例子:
结果并不是[1, 3, 5]而是[5],这有些不可思议。原因在于,in子句在声明时执行, 而条件子句则是在运行时执行。所以上图中的生成器等价于:
09
相同值的不可变对象
可以看到,key=1,value=’a’的键值对神奇地消失了。这里不得不说一下Python字典是使用哈希表的思想实现的,Python 调用内部的散列函数,将键(Key)作为参数进行转换,得到一个唯一的地址,也就是哈希值。而Python 的哈希算法对相同的值计算得到的结果是一样的,这就很好地解释了上述情况出现的原因。
本文列出了在Python学习或者工作中可能会遇到的一些“坑”,虽然不见得每个人都能遇到上述问题,但是可以作为一个参考,以后就能避免踩坑了。希望小伙伴在跟Python打交道的过程中能多注意细节,甚至去了解一些内部实现的原理,这样才能更好地掌握Python这门语言。