element下拉列表触发_Python元组和列表你真的用对了吗?

95b8009d5a62a9133de724b6cfd88307.png

对于Python来说,列表可谓是用到的非常多的数据结构之一了,但是Python还有另外一个数据结构叫做元组,直观表现来说,元组就像是不可变的列表,那么问题来了,元组和列表的区别是什么呢?什么时候应该用元组,什么时候应该用列表呢?我在刚开始学习Python的过程当中一直有这种困惑,本文是我总结的一些关于Python列表和元组的相关知识,下面来一起看一下吧。

基础知识

总的来说,列表和元组实际上都是「一个可以放置任意数据类型的有序数组」, 相比于其他语言,比如说C或者Java等等,他们数组中的元素类型必须保持一致,而Python当中并不存在这个限制,可以在里面放任意的元素。

l = [1, '1', True, None, []]  # 列表中同时包含数字, 字符串, 布尔值, None, 和空列表

tup = (1, '1', True, None, ())  # 元组中同时包含数字, 字符串, 布尔值, None, 和空元组

从表现形式上看来,数组用的是中括号([])而元组用的是小括号(()),他们的区别如下:

  • 「列表是可变的(mutable)」, 长度大小不固定,可以任意的增删改元素
  • 「元组是不可变的(immutable)」, 长度大小固定, 无法对元素删减或者改变

我们通过一个具体的样例来看一下上面的区别,对于列表,我们很容易的利用索引修改当中的元素,而对于元组来说,如果对它进行修改操作,则会触发错误,原因就是元组是不可变的。

l = [1, 2, 3]
l[0] = -1 
print(l)

tup = (1, 2, 3)
tup[0] = -1

我们利用colab来演示结果:

f77faa4e681c88bee9004d3c09025ab0.png

那么问题来了,元组一旦被创建就无法对它进行做任何的改变, 那么如果我们想改变元组当中的值,又该如何操作呢?那只能重新开辟一块内存,创建新的元组了, 同样我们来看下面的一个例子:

l = [1, 2, 3]
print(f'old list addr: {id(l)}')
l += [4]
print(f'new list addr: {id(l)}')

tup = (1, 2, 3)
print(f'old tuple addr: {id(tup)}')
tup += (4, )
print(f'new tuple addr: {id(tup)}')
432520dfd1147920438b918182670e7a.png

可以发现,对于列表进行增加元素的操作,列表本身的内存地址并没有发生改变,而对于元组来说,它本身的内存地址发生了改变,也就符合上面所说的,如果想要对元组中的元素进行新增操作,则会重新开辟一块内存。

基本操作

下面来简单介绍一下列表和元组中的一些常用的基本操作。

索引

对于Python的列表和元组来说,有一个非常强大的功能就是「支持负数索引」,其中-1表示最后一个元素, -2表示倒数第二个元素,以此类推。

l = [1, 2, 3]
print(f'last element in l is {l[-1]}')

tup = (1, 2, 3)
print(f'last element in tup is {tup[-1]}')
208eafc0b662e61e70ba1ae941f62cf1.png

切片

除了索引,Python当中的元组和列表还支持「切片操作」,对于这个我不打算在这里面详细去说,仅介绍简单的基础用法,如有需求,请参考官方文档。

l = [1, 2, 3, 4]
print(f'sub element in l is {l[1:3]}')

tup = (1, 2, 3, 4)
print(f'sub element in tup is {tup[1:3]}')
e012cd2817ec87e280d79afbb9aaf113.png

相互转换

对于列表和元组,可以使用tuple()list()来进行相互的转换。

var = [1, 2, 3, 4]
print(f'type of var is {type(var)}')
var = tuple(var)
print(f'type of var is {type(var)}')
var = list(var)
print(f'type of var is {type(var)}')
96cdcdfffb312e93c01fc79fe15e0089.png

内置函数

下面,我们来看一下列表和元组中的内置函数,这里我只介绍一些比较常用的,对于其他的可以参考官方文档。

count

表示统计列表/元组中元素出现的个数

index

返回列表/元组元素第一次出现的索引

sort & reverse

这两个函数分别对列表的元素进行正序排列和逆序排列(元组是没有这两个函数的, 原因是他会改变列表本身)

sorted & reversed

这两个函数是对于列表和元组进行排序的函数,reversed返回的是迭代器, 而sorted返回的是列表。

下面我们来看一下具体的样例:

l = [3, 3, 4, 5, 5, 4, 3, 2, 1, 1, 2, 3, 3, 2, 2]
tup = (3, 3, 4, 5, 5, 4, 3, 2, 1, 1, 2, 3, 3, 2, 2)

# count
print(f'count of 3 in l is {l.count(3)}')
print(f'count of 3 in tup is {tup.count(3)}')

# index
print(f'index of 5 in l is {l.index(5)}')
print(f'index of 5 in tup is {tup.index(5)}')

# sort & reverse
l.sort()
print(f'sorted l is {l}')
l.reverse()
print(f'reversed l is {l}')

# sorted & reversed
print(f'sorted l is {sorted(l)}')
print(f'reversed l is {reversed(l)}')

print(f'sorted tup is {sorted(tup)}')
print(f'reversed tup is {reversed(tup)}')
872babb5f5404ad5fe807df6ad6b7c2a.png

列表和元组的存储方式

由于列表和元组的特性,列表是可变的,而元组是不可变的,这种差异必然是由于底层的存储方式不同而决定的,首先我们来看下面的样例。

l = [1, ]
print(f'size of l is {l.__sizeof__()}')

tup = (1, )
print(f'size of tup is {tup.__sizeof__()}')
f6f8aa7e68fca601a3ca7c79ec103a75.png

我们可以看到,元组相对于列表来说,在放置相同元素的情况下,元组所占用的空间比列表少了16个字节,主要原因是由于列表是动态的,因此对于列表的存储需要指针,用来指向对应的元素,这里我们存放的是int类型,占用了8个字节,其次由于列表的可变性,需要额外的存储来保存已经分配的长度大小的信息(8个字节),这样才能在空闲长度不足的情况下及时的分配内存开辟空间,我们来看下面的一个例子:

l = []
print(f'initial size of l is {l.__sizeof__()}')
for i in range(5):
  l.append(i)
  print(f'size of l after append {i} is {l.__sizeof__()}')
552efb057e3e1785bc7986618a415f5b.png

我们可以发现,Python为了减少每次列表扩容导致的开销,每次会多分配一定的内存,这样的机制保证了其操作的高效性。而对于元组来说,它本身是不可变的,因此存储空间固定。

列表和元组的性能分析

通过上文对于存储差异的分析,我们可以认为,元组要比列表要更加的轻量一些,因此,总的来说,元组的性能要稍好于列表。

我们来看下面一个列子

python -m timeit 'x=(1,2,3,4,5,6)'
>>> 100000000 loops, best of 3: 0.0106 usec per loop
python -m timeit 'x=[1,2,3,4,5,6]'
>>> 10000000 loops, best of 3: 0.0553 usec per loop

这段代码是在我的电脑上运行的,不同的电脑可能会有差异,但是我们可以看出,对于元组的初始化操作要比列表快5倍左右,原因主要是因为Python会对于一些静态数据做「资源缓存」, 对于元组来说,当它占用的空间不大的时候,Python会暂时缓存这一部分内存,当下次请求同样大小的元组的时候,Python无需向系统再次申请这一部分内存,从而提高了程序的运行效率。

当然,如果对于增删操作来说,列表是优于元组的,原因显而易见,元组是需要重新开辟内存,而列表缺不需要这样。

列表和元组的使用场景

上面分析了这么多,那么什么时候应该用元组,什么时候应该用列表呢?这里当然需要具体情况具体分析。

  1. 如果存储的数据和数量不变,比如保存图片中所有像素点的rgb值,这个显然是利用元组比较合适一些
  2. 如果数据的数量可变,比如需要统计或者计算出来的结果保存,那么显然是利用列表更加合适一些

总结

对于Python当中的元组和列表,从表现形式来看,仅仅只有列表是可变的,而元组是不可变的,但是这个表面特性是由于内部实现而决定的,因此也就造成了对于某些操作的性能差异,因此在选择数据结构的过程中,应该去考虑这一点,选择更加合适的数据结构,总的来说,列表和元组的区别可以总结为如下两点:

  • 列表是可变的,可以任意增加或者删除元素,因此存储空间要略大于元组
  • 元组是不可变的,无法对元素进行增加或者删除,更加轻量,适合保存不变的数据

文章最后,来看一个例子,下面的程序运行结果是怎么样的呢?

t = (1, 2, [30, 40])
t[2] += [50, 60]
  • t变成(1, 2, [20, 30, 40, 50])
  • 因为tuple不支持对他的元素赋值,因此会触发TypeError异常
  • 两个都是错的
  • 两个都是对的

参考资料

  • Fluent Python By Luciano Ramalho
  • Python 官方文档
  • Lists vs Tuples in Python
  • Optimization tricks in Python: lists and tuples | Artem Golubin
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值