Python列表的“坑”与进阶技巧

Python列表的“坑”与进阶技巧

进入我的博客阅读体验更好哦!博客文章链接:Python列表的“坑”与进阶技巧 (www.lxq.icu)

从两个Bug来看Python列表

第一个Bug:列表复制

昨天想要用python矩阵来处理A*算法,首先就需要对图进行表示。因为Python并没有链表,所以我打算使用图的邻接矩阵来表示图。当然若是手动输入一个20×20的矩阵那可太蠢了,理所当然的,我想要先初始化一个二维列表矩阵。于是有了下面的代码:

lis = []
k = []
for i in range(0, 20):
    k.append(0)
for i in range(0, 20):
    lis.append(k)
lis[0][1] = 100  # 0->1距离100
print(lis)

原本还为我的小聪明沾沾自喜——没有使用循环的嵌套进行初始化。然后看到输出的结果我震惊了:

[
  [0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  ...
 ]

❓︎❓︎❓︎关掉IDE重试一遍

于是我重写了个最简单的例程

lis = [[0, 0], [0, 0]]
lis[0][1] = 666
print(lis)
>>[[0,666],[0,0]]

emmm,果然是IDE有问题

列表复制的正确方法

咳咳~言归正传,其实问题就出在lis中的所有k列表共用了一个地址:

for k in lis:
  print(id(k))
>>2563767983296
>>2563767983296
>>2563767983296
...

可以看到所有的k列表地址均为同一个。

那么如何解决呢?

k = [0,0,0]
print(id(k))
>>2030830562240
a = k[:]
print(id(a))
>>2030832878976
b = k.copy()
print(id(b))
>>2030832879616

可以看到,通过 切片的方式[:] 或是使用列表的copy()方法都可以在不同的内存位置开辟新列表,也就不会存在一开始程序出现的问题了。

列表初始化的简洁方法

那么如何解决列表(数组)的初始化呢?这里给出一个简洁的方法:

# 一维列表
lis = [0] * 3
print(lis)
>>[0, 0, 0]
# [表达式 for 变量 in 列表]
lis=[0 for i in range(3)]
print(lis)
>>[0, 0, 0]
# 二维列表 
lis = [[0, 0, 0]] * 3
print(lis)
>>[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
# [表达式 for 变量 in 列表]
lis = [[0 for i in range(3)] for j in range(3)]
print(lis)
>>[[0, 0, 0], [0, 0, 0], [0, 0, 0]]

当然还有进阶的方法——在初始化列表时加入if判断。但此方法使得一行代码太过集中,有炫技之嫌,请谨慎使用。

k = [1, 2, 3, 4]
lis = [x**2 for x in k if x > 2]
print(lis)
>>[9, 16]

第二个Bug:列表遍历时删除元素

来到第二个场景,在拿到数据后,特别是爬虫爬完数据后我们往往会进行数据清洗。比如在遍历列表的过程中删除满足特定条件的元素,于是有了下面一个例程:

lis = [1, 2, 2, 3]
for i in lis:
  if i == 2:
    lis.remove(i)
print(lis)
>>[1, 2, 3]

本来是需要删除列表中等于2的元素,为何处理后的列表中还有2呢?我们换一种遍历方法试一试:

lis = [1, 2, 2, 3]
for i in range(0, len(lis)):
  if lis[i] == 2:
    lis.remove(lis[i])
print(lis)
>>IndexError: list index out of range

这次直接报错了,说明确实发生了一些问题,看错误是索引超过了列表范围,那么我们来看看列表在删除操作前后的长度如何:

lis = [1, 2, 2, 3]
for i in range(0, 4):
  if lis[i] == 2:
  	print(len(lis))
    lis.remove(lis[i])
    print(len(lis))
>>4
>>3

可以看到在删除操作进行的同时,列表的长度也随之进行了变化,lis[3]在原有的列表是正确的,但如果在删除了一个元素的列表中就变得非法了,所以报错了。

那么再回到开始的例程,为什么输出结果是[1, 2, 3]呢?我认为for i in lis在每次循环后会比较循环下表与数组长度,所以不会和for i in range(0, len(lis))一样报错,但是这并非就没有问题了,看下面这个输出:

lis = [1, 2, 2, 3]
for i in lis:
  if i == 2:
    lis.remove(i)
    print(lis[1])
>>2

在删除列表中第一个2元素时,原本处于lis[2]的2元素被前移到了lis[1],并被Python误以为lis[1]已经经过了处理从而跳过了处理第二个2元素,导致了错误。看下面这个例程你就会发现这十分有趣:

lis = [2, 2, 2, 2, 2, 2]
for i in lis:
  if i == 2:
    lis.remove(i)
print(lis)
>>[2, 2, 2]

若两个元素相邻,处理了第一个元素后第二个元素就会被忽略,就造成了上述的这种情形。

解决方法

对于此类问题,我通常会创建一个临时列表存储需要删除的数据,通过比对两个列表来进行数据清洗:

lis = [1, 2, 2, 3]
temp = []
for i in lis:
  if i == 2:
    temp.append(i)
for i in lis:
  for j in temp:
    if i == j:
      lis.remove(i)
print(lis)
>>[1,3]
比较两种遍历方法

for i in lisfor i in range(0, len(lis))是我们在python中两种常用的遍历列表的方法,根据上述的举例不难看出,前者的鲁棒性更好,但与此同时可能会产生隐含的错误,而后者虽然可能会出现index out of range的报错,但对于开发是较为友好的。

Python列表的一些补充

切片

当然,基础的Python列表的切片方法就不赘述了,我想要扩展的是字符串也是可以使用切片的,比如:

a = 'python'
b = a[:-2]
print(b)
>>pyth

此外,切片还可以用于间隔输出或是倒序输出,甚至倒序间隔输出:

a = 'python'
b = a[::2]
print(b)
>>pto
c = a[::-1]
print(c)
>>nohtyp
d = a[::-2]
print(d)
>>nhy

Python列表的存储方式

看下面这个例程:

lis = [1, 2, 3]
for i in lis:
  print(id(i))
>>140717470979856
>>140717470979888
>>140717470979920

可以看到Python列表中的元素并不是顺序存储的而是链式结构的,这也是为什么Python列表的长度是可变的。如果是顺序存储若不定义列表长度那么在存储空间中就可能发生冲突。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值