Python基础回顾:List vs Tuple

前言

面试中,会被问到一个题目是,List与Tuple的不一样,以及为何须要二者。今天就把这个知识点整理一下。python

List与Tuple基础

List和Tuple,即列表和元组,都是一个能够放置任意数据类型的有序集合。不一样的是,列表是可变的(mutable),元组是不可变的(immutable)(关于二者差别,咱们放到下一节详细说明)。面试

  • 列表和元组都支持负数索引
l = [1,2,3,4]
1[-1]
4
t = (1,2,3,4)
t[-1]
4
复制代码
  • 除了基本的初始化、索引以外,列表和元组都支持切片操做
l = [1,2,3,4]
l[1:3]
[2, 3]
t = (1,2,3,4)
t[1:3]
(2, 3)
复制代码
  • 列表和元组都可任意嵌套,能够经过list()和tuple()函数相互转换
l = [[1, 2, 3], [4, 5]] # 列表的每个元素也是一个列表
t = ((1, 2, 3), (4, 5, 6)) # 元组的每个元素也是一个元组

list((1,2,3))
[1,2,3]
tuple([1,2,3])
(1,2,3)
复制代码
  • 经常使用的内置函数基本相同
l = [3, 2, 3, 7, 8, 1]
l.count(3) 
2
l.index(7)
3
l.reverse()
l
[1, 8, 7, 3, 2, 3]
l.sort()
l
[1, 2, 3, 3, 7, 8]

tup = (3, 2, 3, 7, 8, 1)
tup.count(3)
2
tup.index(7)
3
list(reversed(tup))
[1, 8, 7, 3, 2, 3]
sorted(tup)
[1, 2, 3, 3, 7, 8]
复制代码

注意,list.reverse() 和 list.sort() 分别表示原地倒转列表和排序,但元组没有内置的这两个函数)。reversed() 和 sorted() 一样表示对列表 / 元组进行倒转和排序,可是会返回一个倒转后或者排好序的新的列表 / 元组。缓存

List与Tuple的不一样

上一小节,咱们简单回顾了一下列表和元组的基础,这一小节,咱们来来细品列表和元组的不一样。app

一言以蔽之,核心不一样即,列表是可变对象(mutable),而元组是不可变对象(immutable)。正由于如此,才致使了二者从三个方面有些差别。函数

1.性能方面

一般来讲,由于有垃圾回收机制的存在,若是一些变量不被使用了,Python会回收它们所占用的内存,返还给操做系统,以便其余变量或其余应用使用。但对于一些不可变对象,好比元组,若是它不被使用而且占用空间不大时,Python会暂时缓存这部份内容,这样下次再建立一样大小的元组时,Python就能够不须要向操做系统发出请求,寻找内存,而是直接分配以前缓存的内容空间。这样能大大加快程序的运行速度。oop

咱们能够用python3 -m timeit -s '<operation>'来观察一下具体操做的耗时状况。性能

初始化性能

python3 -m timeit 'a=[1,2,3,4,5,6,7,8,9,10]'
2000000 loops, best of 5: 107 nsec per loop
python3 -m timeit 'b=(1,2,3,4,5,6,7,8,9,10)'
20000000 loops, best of 5: 17.5 nsec per loop
复制代码

能够看到,元组初始化耗时,大概是列表的6倍。在初始化方面,元组性能胜。优化

索引操做性能

python3 -m timeit -s 'a=[1,2,3,4,5,6,7,8,9,10]' -s 'a[3]'
20000000 loops, best of 5: 11.4 nsec per loop
python3 -m timeit -s 'b=(1,2,3,4,5,6,7,8,9,10)' -s 'b[3]'
20000000 loops, best of 5: 11.4 nsec per loop
复制代码

差异不大,平手。spa

追加修改的性能

python3 -m timeit \
-s "L = []" \
-s "x = range(10000)" \
"for item in x:" " L.append(item)"
200 loops, best of 5: 1.14 msec per loop

python3 -m timeit \
-s "T = ()" \
-s "x = range(10000)" \
"for item in x:" " T += (item,)"
2 loops, best of 5: 400 msec per loop
复制代码

能够看到,在追加时,若是咱们用元组,耗时大概是列表追加的350倍。由于元组是不可变对象,每次追加,都是新建立一个元组。操作系统

2.内存效率

首先,咱们看一个例子:

l = [1,2,3]
l.__sizeof__()
64
t = (1,2,3)
t.__sizeof__()
48
复制代码

对于列表和元组,虽然初始化时放了相同的元素,可是元组的存储空间,比列表要少16个字节。这是由于,因为列表是可变对象,须要存储指针,来指向对应的元素(上述例子中,int型,8字节)。另外,因为列表可变,须要额外存储已经分配的长度大小(8字节),这样才能够实时追踪列表空间的使用状况,当空间不足时,及时分配额外空间。

l = []
l.__sizeof__() // 空列表的存储空间为40字节
40
l.append(1)
l.__sizeof__() 
72 // 加入了元素1以后,列表为其分配了能够存储4个元素的空间 (72 - 40)/8 = 4
l.append(2) 
l.__sizeof__()
72 // 因为以前分配了空间,因此加入元素2,列表空间不变
l.append(3)
l.__sizeof__() 
72 // 同上
l.append(4)
l.__sizeof__() 
72 // 同上
l.append(5)
l.__sizeof__() 
104 // 加入元素5以后,列表的空间不足,因此又额外分配了能够存储4个元素的空间
复制代码

从上面的例子中,咱们能够看到列表空间分配的大概过程。为了减小每次增长、删除操做时空间分配的开销,Python每次分配空间时会额外多分配一些。这样的机制(over-allocating)保证了其操做的高效性;增长、删除操做的时间复杂度为O(1)。

但因为元组是不可变的,长度大小固定,故存储空间固定。那么,咱们能够获得一个初步结论,对于不可变对象来讲,有这样一个好处,在于内存更有效,占用更小。

此外,对于不可变对象,好比String,在Python3.6以前,是有一个String Interning的概念的,即以下所示:

c = "hi"
d = "hi"
id(c)
4561935656
id(d)
4561935656
复制代码

在CPython中,若是建立几个不可变对象,但都具备相同的值,Python能够将这几个对象指向一个。这种优化在3.6以后就取消了,可是不妨碍这种操做更节约内存、节约时间。因此,在内存效率层面,不可变对象,元组更胜一筹。

3.工程层面

在项目中,使用元组或者列表,还要给予工程角度去考量,好比返回一个地理位置的坐标,必定是元组更合适——不可变对象,值固定。在某些场景下,使用元祖,还能够避免debug过程当中值变动带来的一些失误。这就是仁者见仁智者见智的事情了。

Reference

[1].Python: What is the Difference between a List and a Tuple?

相关文章
相关标签/搜索