1、可变对象(mutable)作为默认参数引发的陷阱
- 不可变对象:该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。例如:数值类型(int和float)、字符串str、元组tuple、None都是不可变类型。
- 可变对象:该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的出地址,通俗点说就是原地改变。列表list、字典dict、集合set都是可变类型。
下面以list作为默认参数为例,看看能引发什么错误:
>>> def test(L = []):
L.append('end')
return L
>>> test(['A','B']) # 结果是想要的
['A', 'B', 'end']
>>> test([1,2]) # 结果也是想要的
[1, 2, 'end']
>>> test() # 结果还是想要的
['end']
>>> test() # 结果是......
['end', 'end']
>>> test() # eeeeee
['end', 'end', 'end']
>>> test(['a','b','c']) # 结果正常
['a', 'b', 'c', 'end']
好像只有用默认参数时才会出现非预想的结果,好像test()
记住了第一次创建的默认参数L
一样,导致后面每次调用test()
时都会在[]
中加一个'end'
。
解释:Python函数在定义的时候,默认参数L
的值就被计算出来了,即[]
,因为默认参数L
也是一个变量,它指向对象[]
,每次调用该函数,如果改变了L
的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]
了。
解决办法:用不可变对象代替。代码如下:
>>> def test(L = None):
if L is None:
L = []
L.append('end')
return L
>>> test()
['end']
>>> test()
['end']
2、x = x + y
与x += y
的小区别
x = x + y
与x += y
在大多数情况下是等价的:
例如:
>>> x = 1
>>> x = x + 1
>>> print(x)
2
>>> x = 1
>>> x += 1
>>> print(x)
2
# 好像没什么区别,再看一个例子
>>> x = [1]
>>> x = x + [2]
>>> print(x)
[1, 2]
>>> x = [1]
>>> x += [2]
>>> print(x)
[1, 2]
# 好像还是没事么区别,好像打脸了!!!接着往下看:
>>> x = [1]
>>> print(id(x)) # id(x):返回对象x的内存地址,别问我为什么我的地址和你们的不一样!!!
2841626172744
>>> x = x + [2]
>>> print(id(x)) # 为什么两次x的地址不一样???
2841626172616
# 再试试另一个
>>> x = [1]
>>> print(id(x))
2841626173384
>>> x += [2]
>>> print(id(x)) # 两次x的地址一样,好像发现了什么!!!
2841626173384
解释:首先,当x
是不可变对象时,这两者运算方式确实没什么区别,都会返回一个新地址。但当x
是可变对象时,就有点区别:程序执行x = x + y
时,是先开辟一段新内存,将计算x + y
的结果放入其中,再让等号左边的x
指向这段新内存。程序执行x += y
时,是在原来x
的地址上直接改成x + y
计算后的结果。
3、小括号()
引发的错误
这个错误大家在学习tuple
时应该会讲到,就不细说,见如下代码段:
>>> type((1,2))
<class 'tuple'>
>>> a = (1,2)
>>> type(a)
<class 'tuple'>
>>> a = (1)
>>> type(a)
<class 'int'>
>>> a = (1,)
>>> type(a)
<class 'tuple'>
解释:在python中,(1,2)
是tuple
类型,只有一个元素的tuple
是(1,)
,而不是(1)
,(1)
在python中会优先执行数学运算中的小括号的含义,即a = (1)
和a = 1
是等价
4、列表的乘法
列表的加法大家应该都用过,例如:
>>> [1,2,3]+[4,5]
[1, 2, 3, 4, 5]
对于乘法不知大家是否用过:
>>> a = [1] * 3
>>> a
[1, 1, 1]
>>> a = [None] * 5
>>> a
[None, None, None, None, None]
# 貌似还挺好用,没什么问题。
>>> a = [[]]*5
>>> a
[[], [], [], [], []]
>>> a[0].append(1)
>>> a[0]
[1]
>>> a
[[1], [1], [1], [1], [1]] # 一定是错觉,再试一次
>>> a[0].append(2)
>>> a
[[1, 2], [1, 2], [1, 2], [1, 2], [1, 2]] # eeeeeee
解释:上述的写法中,a
中的每一个元素(空的list[]
)其实都是指向同一个可变对象[]
,所以当一个变了的时候,就都变了。
解决办法:
- 法一:列表内的元素不用可变对象,改用不可变对象。
- 法二:使用列表生成式,代码如下:
>>> a = [[] for x in range(5)]
>>> a
[[], [], [], [], []]
>>> a[0].append(1)
>>> a
[[1], [], [], [], []]
5、访问列表的同时修改列表
最容易出现这种错误的情景是:一边遍历着列表,一边筛选着列表元素,并将不满足条件的删除。
例如:
# test函数作用是:若L中的元素是偶数,则删除该元素。
>>> def test(L):
for index, value in enumerate(L):
"""
enumerate(sequence, [start=0])
函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
例如:
>>>seasons = ['Spring', 'Summer', 'Fall', 'Winter']
>>> list(enumerate(seasons)
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
"""
if 0 == value%2:
L.pop(index)
print(L)
# 测试一下
>>> L = [0,1,2,3,4,5]
>>> test(L)
[1, 3, 5] # 好像还不错
>>> L = [0,1,2,2,3,4,4,5]
>>> test(L)
[1, 2, 3, 4, 5] # 2和4,eeeee!!!!!
解释:简单来说一句话,“list长度在变短的同时index在增加,所以错位了”。不再多解释,自己去体会。
解决办法:使用列表生成式,如下:
>>> L = [0,1,2,3,4,5]
>>> [x for x in L if 0!=x%2]
[1, 3, 5]
>>> L = [0,1,2,2,3,4,4,5]
>>> [x for x in L if 0!=x%2]
[1, 3, 5]
6、闭包问题
起初,我还没注意到这个问题,有一次当我需要写一个输出2~1000
之间的素数时,发现了可以用埃拉托色尼筛选法求解,链接的地址上也有python实现源码,至于我有什么疑问,最终如何解决的,暂且先不谈。看完这节后,大家应该就知道了。好了,不多说,先看一段程序:
>>> def test():
# 函数本意是想将L中的元素过滤一遍,将2、3、5的倍数删掉。
L = range(2,11) # L = [2,3,4,5,6,7,8,9,10]
for n in [2,3,5]:
L = filter(lambda x: x%n > 0, L)
print(list(L))
>>> test()
[2, 3, 4, 6, 7, 8, 9] # 但结果,好像只是把5的倍数去掉了。
解释:要明白上面程序为何会这样运行,需明白以下几个知识(多余的我就不解释了,自己体会):
- python中的闭包、迟绑定问题
- python中的LEGB问题
- python中的
lambda()
和filter()
解决办法:
- 法一:改变变量的作用域(详情见代码):
>>> def test():
L = range(2,11) # L = [2,3,4,5,6,7,8,9,10]
for n in [2,3,5]:
L = filter(lambda x, n=n: x%n > 0, L) # 此处有改变
print(list(L))
>>> test() # 结果正确
[7]
- 法二:再创建一个函数,绑定循环变量(不如上一个方法来的简单,详情见代码):
>>> def f(n):
return lambda x: x%n > 0
>>> def test():
L = range(2,11) # L = [2,3,4,5,6,7,8,9,10]
for n in [2,3,5]:
L = filter(f(n), L)
print(list(L))
>>> test() # 结果也正确
[7]