Python 常见的坑汇总

1. 列表与 * 操作

Python 中,* 操作符与 list 结合使用,实现元素复制。

复制 5 个空列表:

In [1]: [[]] * 5
Out[1]: [[], [], [], [], []]

In [2]: a = [[]] * 5

In [3]: a
Out[3]: [[], [], [], [], []]

In [4]: 

填充两个元素:

In [4]: a[0].extend([1,2,3])

In [5]: a[1].extend([4,5,6])

期望应该为:

[[1,3,5],[2,4,6],[],[]]

实际为:

In [6]: a
Out[6]: 
[[1, 2, 3, 4, 5, 6],
 [1, 2, 3, 4, 5, 6],
 [1, 2, 3, 4, 5, 6],
 [1, 2, 3, 4, 5, 6],
 [1, 2, 3, 4, 5, 6]]

原来 * 操作复制出的 a[0]a[1]...a[5],在内存中标识符是相等的,实现的仅仅是浅复制。

In [7]: id(a[0])
Out[7]: 1641831703560

In [8]: id(a[1])
Out[8]: 1641831703560

In [9]: id(a[2])
Out[9]: 1641831703560

In [10]: id(a[3])
Out[10]: 1641831703560

在这种场景下,希望实现 id[0]id[1] 不相等,修改 a[1] 不会影响 a[0]
不使用 *,使用列表生成式,复制出 5 个不同 id 的内嵌列表,这样就能避免赋值互不干扰的问题。

In [12]: b = [[] for _ in range(5)]

In [13]: b
Out[13]: [[], [], [], [], []]

In [14]: id(b[0])
Out[14]: 1641875609928

In [15]: id(b[1])
Out[15]: 1641875595080

In [16]: id(b[2])
Out[16]: 1641875822344

In [17]: b[0].extend([1,2,3])

In [18]: b[1].extend([4,5,6])

In [19]: b
Out[19]: [[1, 2, 3], [4, 5, 6], [], [], []]

In [20]: 

2. 删除列表元素

假设列表有重复元素,要删除列表中指定的元素,看下面代码:

In [20]: def del_list(a, x):
    ...:     for i in a:
    ...:         if i == x:
    ...:             a.remove(i)
    ...:     return a
    ...:     

In [21]: a = [1,2,3,2,4]

In [22]: del_list(a, 2)
Out[22]: [1, 3, 4]

In [23]: del_list([1,2,3,4,3,4,3], 2)
Out[23]: [1, 3, 4, 3, 4, 3]

In [24]: del_list([1,2,3,4,3,4,3], 3)
Out[24]: [1, 2, 4, 4]

In [25]: del_list([1,3,5,3,2], 3)
Out[25]: [1, 5, 2]

可以看到上面的删除都是正确的,再来看下面的代码

In [26]: del_list([1,3,3,3,3,5], 3)
Out[26]: [1, 3, 3, 5]

可以看出,删除结果仍然是包含 3 , 为什么呢?

遍历列表 aremove 一次,移掉位置 i 后的所有元素索引都要减一。所以,一旦删除的元素,重复出现在列表中,就总会漏掉一个该删除的元素。

正确做法,找到被删除元素后,删除,同时下次遍历索引不加一;若未找到,遍历索引加一。

一般来讲,尽量避免在列表迭代的过程中对列表进行删除、更新操作。

3. 函数默认参数为空

Python 函数的参数可设为默认值。如果一个默认参数类型为 list ,默认值为设置为 []
有下面函数:

In [27]: def add_list(value, volume=[]):
    ...:     if volume is None:
    ...:         volume = []
    ...:     length = len(volume)
    ...:     for i in range(length):
    ...:         volumn[i] = i + value
    ...:     return volume
    ...: 


调用 add_list 函数, val 值为 10, volume 默认值,函数返回 ret 为空列表。

In [28]: ret = add_list(10)

In [29]: ret
Out[29]: []

In [30]: 

然后,我们向空列表 ret 中,分别添加值 1、2,打印 ret ,结果符合预期

In [30]: ret.append(1)

In [31]: ret.append(2)

In [32]: ret
Out[32]: [1, 2]

同样方法,再次调用 add_list 函数,第二个参数还是取默认值。预期返回值 ret 还是空列表,但是结果却出人意料!

In [41]: ret = add_list(10)

In [42]: ret
Out[42]: [10, 11]

为什么返回值为 [10,11] 呢? 按照出现的结果,我们猜测 [1, 2] + 10 后,不正是 [11,12]。

原来调用函数 add_list 时,默认参数 volume 取值为默认值时,并且 volume 作为函数的返回值。再在函数外面做一些操作,再次按照默认值调用,并返回。整个过程,默认参数 volumeid 始终未变。

通过增加打印我们可以看到,确实是默认参数 volumeid 始终未变。

In [45]: ret = add_list(10)
volume id is 1641863237000

In [46]: ret.append(1)

In [47]: ret.append(2)

In [48]: ret 
Out[48]: [1, 2]

In [49]: ret = add_list(10)
volume id is 1641863237000

In [50]: ret
Out[50]: [10, 11]

为了避免这个隐藏的坑,函数的默认参数值切记不能设置为 [],而是为 None。这样即便按照默认值调用多次,也会规避此风险。

In [53]: def add_list(value, volume=None):
    ...:     print("volume id is {}".format(id(volume)))
    ...:     if volume is None:
    ...:         volume = []
    ...:     length = len(volume)
    ...:     for i in range(length):
    ...:        volume[i] = i + value
    ...:     return volume
    ...:     

In [58]: ret = add_list(10)
volume id is 1929553104

In [59]: ret
Out[59]: []

In [60]: ret.append(1)

In [61]: ret.append(2)

In [62]: ret
Out[62]: [1, 2]

In [63]: ret = add_list(10)
volume id is 1929553104

In [64]: ret
Out[64]: []

4. {} 和 ()

单个元素要被识别为元组,必须在括号后面加个逗号 , 详见如下代码:

In [65]: a = (10)

In [66]: type(a)
Out[66]: int

In [67]: b = (10,)

In [68]: type(b)
Out[68]: tuple

创建集合与字典,它们都用一对 {},但是默认返回字典,而不是集合。要想创建空集合,可使用内置函数 set()

In [69]: d = {}

In [70]: type(d)
Out[70]: dict

In [71]: s = set()

In [72]: type(s)
Out[72]: set

5. 多值赋值顺序


多值赋值是先计算出等号右侧的所有变量值后,再赋值给等号左侧变量。

In [73]: a, b = 1, 2

In [74]: a, b = b+1, a+b 

In [75]: a, b
Out[75]: (3, 3)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wohu007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值