列表 VS 元组
一个 Python 初学者的普遍疑问:列表与元组的区别是什么?
答案是:它们之间有两点不同,并且二者之间有着复杂的相互作用。它们分别是”技术上”的差异和“文化上”的差异。
首先,有一点是相同的:列表与元组都是容器,是一系列的对象。
1 2 3 4 5 6 |
>>> my_list = [1, 2, 3] >>> type(my_list) <class 'list'> >>> my_tuple = (1, 2, 3) >>> type(my_tuple) <class 'tuple'> |
二者都可以包含任意类型的元素甚至可以是一个序列,还可以包含元素的顺序(不像集合和字典)。
1 2 3 4 5 6 7 |
>>> my_list[1] = "two" >>> my_list [1, 'two', 3] >>> my_tuple[1] = "two" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment |
现在来讲讲区别。列表和元组的“技术差异”是,列表是可变的,而元组是不可变的。这是在 Python 语言中二者唯一的差别。
尽管有好几种表现方式,但这是列表和元组唯一的“技术差异”。比如:列表有一个 append() 的方法来添加更多的元素,而元组却没有这个方法:
1 2 3 4 5 6 7 |
>>> my_list.append("four") >>> my_list [1, 'two', 3, 'four'] >>> my_tuple.append("four") Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'tuple' object has no attribute 'append' |
元组并不需要一个 append()
方法,因为元组不能修改。
“文化差异“是指二者在实际使用中的差异:在你有一些不确定长度的相同类型队列的时候使用列表;在你提前知道元素数量的情况下使用元组,因为元素的位置很重要。
举个例子,假设你有一个函数是用来在目录中查找结尾为 *.py 的文件。函数返回的应该是一个列表,因为你并不知道你会找到多少个文件,而这些文件都有相同的含义:它们仅仅是你找到的另一个文件。
1 2 |
>>> find_files("*.py") ["control.py", "config.py", "cmdline.py", "backward.py"] |
另一方面,让我们假设你需要存储五个值来代表气象观测站的位置:id ,城市,国家,纬度,经度。元组就比列表更适用于这种情况:
1 2 3 |
>>> denver = (44, "Denver", "CO", 40, 105) >>> denver[1] 'Denver' |
(让我们暂时不要讨论使用类来表示的这种情况)。第一个元素是 id ,第二个元素是城市,等等。位置决定了它的意义。
把这种“文化差异”放到 C 语言来讲,列表像是数组,元组则像是 structs 结构体。
Python 采用了命名元组的方法来使含义更加明确:
1 2 3 4 5 6 7 8 9 |
>>> from collections import namedtuple >>> Station = namedtuple("Station", "id, city, state, lat, long") >>> denver = Station(44, "Denver", "CO", 40, 105) >>> denver Station(id=44, city='Denver', state='CO', lat=40, long=105) >>> denver.city 'Denver' >>> denver[1] 'Denver' |
对元组和列表之间“文化差异”的一个总结是:元组是不需要名字的命名元组。
“技术差异”和“文化差异”这种区分也并不准确,因为有时候他们并不稳定。为什么同种元素的序列可变,而不同元素的序列不可变呢?举个例子,我不能修改我的气象站,因为命名元组是不可变的元组。
1 2 3 4 |
>>> denver.lat = 39.7392 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: can't set attribute |
有时技术考量要比文化考量重要。比如你不能把列表当做字典的关键字,因为只有不可变的值才能进行哈希运算,因此只有不可变的值才能作为关键字。要使用列表做关键字,你需要把它转化为元组:
1 2 3 4 5 6 7 8 9 |
>>> d = {} >>> nums = [1, 2, 3] >>> d[nums] = "hello" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list' >>> d[tuple(nums)] = "hello" >>> d {(1, 2, 3): 'hello'} |
技术和文化的另一个冲突是:在 Python 中元组使用的地方,列表也起着重要作用。当你定义一个带参函数,元组作为参数传入,即使这个值的位置并不显著,至少 Python 知道这是一个元组。你可能会说这是个元组因为你不能改变被传进来的值,但这只是重视了技术上的差异而非文化上的差异。
我知道:在参数数组中,位置很重要,因为他们是位置参数。但是在函数中,它会接收参数数组并传入另一个函数,它仅仅是一组参数,与其他参数并无不同。其中一些会在调用中变化。
Python 之所以在这里使用元组是因为元组比列表更节省空间。列表被重复分配使得在添加元素上更快。这体现了 Python 的实用性:相比于在参数数组上,列表/元组的语义差别,在这种情况下,使用这种数据结构更好。
大部分情况下,你应该根据文化差异来选择使用列表还是元组。思考下你数据的含义。如果实际上根据你的程序来计算,你的数据长度并不固定,那么可能用列表更好。如果在写代码时你知道第三个元素的含义,那么用元组更好。
另一方面,函数式编程强调使用不可变的数据结构来避免产生使代码变得更难解读的副作用。如果你喜欢使用函数式编程,那么你可能因为元组的不可变而更喜欢它们。
那么:你应该用元组还是列表?答案是:永远没有一个简单的答案。