Python数据结构-列表与元组


列表(list)和元组(tuple)是 Python 中最常用的两种序列类型,均用于存储有序的元素集合。它们的核心区别在于:列表是可变的(元素可修改、新增、删除),而元组是不可变的(元素创建后无法修改)。这种差异决定了它们的使用场景和操作方式。

一、列表(list):可变的序列

列表是 Python 中最灵活的序列类型,用方括号 [] 表示,元素之间用逗号分隔,支持任意数据类型(甚至可嵌套其他列表)。

1. 列表的创建

# 直接用 [] 创建
empty_list = []  # 空列表
num_list = [1, 2, 3, 4]  # 整数列表
mix_list = [1, "hello", True, [5, 6]]  # 混合类型(含嵌套列表)

# 用 list() 函数创建(可转换其他序列,如字符串、元组)
str_to_list = list("python")  # 字符串转列表 → ['p', 'y', 't', 'h', 'o', 'n']
tpl_to_list = list((10, 20, 30))  # 元组转列表 → [10, 20, 30]

2. 列表的核心操作(因可变,支持修改元素)

(1)访问与修改元素(通过索引)
lst = [10, 20, 30, 40]
print(lst[1])  # 访问索引1的元素 → 20
lst[1] = 200   # 修改元素 → 列表变为 [10, 200, 30, 40]
(2)添加元素
  • append(x):在列表末尾添加单个元素 x(常用)。
lst = [1, 2]
lst.append(3)  # → [1, 2, 3]
  • insert(index, x):在指定索引 index 处插入元素 x(后续元素后移)。
lst = [1, 2, 3]
lst.insert(1, 100)  # 在索引1处插入100 → [1, 100, 2, 3]
  • extend(iterable):将另一个可迭代对象(如列表、元组)的所有元素添加到末尾(等价于 +=)。
lst = [1, 2]
lst.extend([3, 4])  # → [1, 2, 3, 4]
lst += (5, 6)       # 等价于 extend → [1, 2, 3, 4, 5, 6]
(3)删除元素
  • del lst[index]:删除指定索引的元素(通用语法,不仅限于列表)。
lst = [1, 2, 3]
del lst[1]  # 删除索引1的元素 → [1, 3]
  • remove(x):删除第一个值为 x 的元素(若 x 不存在则报错)。
lst = [1, 2, 2, 3]
lst.remove(2)  # 删除第一个2 → [1, 2, 3]
  • pop(index):删除指定索引的元素并返回该元素(默认删除最后一个元素)。
lst = [1, 2, 3]
last = lst.pop()   # 删除最后一个元素,返回3 → 列表变为 [1, 2]
first = lst.pop(0) # 删除索引0的元素,返回1 → 列表变为 [2]
(4)排序与反转
  • sort(key=None, reverse=False)原地排序(修改原列表,默认升序)。
lst = [3, 1, 4, 2]
lst.sort()  # 升序 → [1, 2, 3, 4]
lst.sort(reverse=True)  # 降序 → [4, 3, 2, 1]
  • sorted(iterable)返回新列表(不修改原列表,功能同 sort)。
lst = [3, 1, 4, 2]
new_lst = sorted(lst)  # 原列表不变,new_lst = [1, 2, 3, 4]
  • reverse()原地反转列表元素顺序。
lst = [1, 2, 3]
lst.reverse()  # → [3, 2, 1]
(5)其他常用方法
  • count(x):返回元素 x 在列表中出现的次数。
lst = [1, 2, 2, 3, 2]
print(lst.count(2))  # → 3
  • index(x):返回第一个值为 x 的元素的索引(若 x 不存在则报错)。
lst = [10, 20, 30]
print(lst.index(20))  # → 1
  • clear():清空列表(变为空列表)。
lst = [1, 2, 3]
lst.clear()  # → []

二、元组(tuple):不可变的序列

元组用圆括号 () 表示(也可省略括号),元素创建后无法修改(不可增删改),因此比列表更轻量、性能更好。

1. 元组的创建

# 直接用 () 创建
empty_tpl = ()  # 空元组
num_tpl = (1, 2, 3, 4)  # 整数元组
mix_tpl = (1, "hello", True, (5, 6))  # 混合类型(含嵌套元组)

# 单元素元组:必须加逗号(否则会被视为普通括号)
single_tpl = (10,)  # 正确的单元素元组
not_tpl = (10)      # 不是元组,而是整数10

# 省略括号创建(逗号是关键)
tpl = 1, 2, 3  # 等价于 (1, 2, 3)

# 用 tuple() 函数创建(转换其他序列)
list_to_tpl = tuple([1, 2, 3])  # 列表转元组 → (1, 2, 3)
str_to_tpl = tuple("python")    # 字符串转元组 → ('p', 'y', 't', 'h', 'o', 'n')

2. 元组的核心操作(因不可变,仅支持查询类操作)

元组支持序列的通用操作(索引、切片、相加、相乘、in 检查等),但不支持修改元素(否则报错)。

(1)访问元素(索引、切片)
tpl = (10, 20, 30, 40)
print(tpl[1])     # 访问索引1 → 20
print(tpl[1:3])   # 切片 → (20, 30)
print(tpl[::-1])  # 反向切片 → (40, 30, 20, 10)
(2)不可修改元素(关键特性)
tpl = (1, 2, 3)
tpl[0] = 0  # 报错:TypeError(元组不可修改)
del tpl[0]  # 报错:TypeError(不能删除元素)
(3)常用方法(仅2个,因不可变)
  • count(x):返回元素 x 出现的次数(同列表)。
tpl = (1, 2, 2, 3)
print(tpl.count(2))  # → 2
  • index(x):返回第一个 x 的索引(同列表)。
tpl = (10, 20, 30)
print(tpl.index(20))  # → 1
(4)特殊点:元组中的可变元素可修改

若元组包含可变元素(如列表),则该元素内部可修改(元组本身的结构未变,只是元素的引用指向的对象被修改)。

tpl = (1, [2, 3], 4)
tpl[1].append(5)  # 元组中的列表是可变的,可修改 → 元组变为 (1, [2, 3, 5], 4)

三、列表与元组的对比

特性 列表(list) 元组(tuple)
语法 方括号 [] 圆括号 ()(可省略)
可变性 可变(支持增删改元素) 不可变(创建后无法修改)
性能 稍低(需维护可变结构) 更高(内存占用少,访问快)
适用场景 存储动态数据(需频繁修改) 存储固定数据(如配置、坐标)
作为字典键 不可(因可变,哈希值不固定) 可(因不可变,哈希值固定)
常用方法 丰富(append、sort等) 较少(仅count、index)

四、使用场景建议

  • 用列表:当数据需要动态更新时(如用户列表、商品库存),或需要频繁添加/删除元素时。
  • 用元组:当数据固定不变时(如经纬度 (39.9, 116.3)、函数返回多值 return a, b),或需要作为字典的键时。

总结

列表和元组都是有序序列,核心差异在于可变性。理解这一点后,可根据数据是否需要修改来选择合适的类型,以提高代码的安全性和性能。

1.列表和元组

1)列表和元组,都是一个可以放置任意数据类型的有序集合

2)两者区别:

  • 列表是动态的,长度大小不固定,可以随意地增加、删减或者改变元素
  • 而元组是静态的,长度大小固定,无法增加删减或者改变

可是,如果你想对已有的元组做任何"改变",该怎么办呢?那就只能重新开辟一块内存,创建新的元组了。

3)Python 中的列表和元组都支持负数索引

4)列表和元组都支持切片操作

5)列表和元组都可以随意嵌套

6)两者也可以通过 list() 和 tuple() 函数相互转换

list((1, 2, 3))
[1, 2, 3]

tuple([1, 2, 3])
(1, 2, 3)

7)列表和元组常用的内置函数

count(item) 表示统计列表 / 元组中 item 出现的次数。

index(item) 表示返回列表 / 元组中 item 第一次出现的索引。

list.reverse() 和 list.sort() 分别表示原地倒转列表和排序(注意,元组没有内置的这两个函数)。

reversed() 和 sorted() 同样表示对列表 / 元组进行倒转和排序,reversed() 返回一个倒转后的迭代器(上文例子使用 list() 函数再将其转换为列表);sorted() 返回排好序的新列表。

8)列表和元组的性能

元组要比列表更加轻量级一些,所以总体上来说,元组的性能速度要略优于列表。

9)列表和元组的使用场景

  1. 如果存储的数据和数量不变,比如你有一个函数,需要返回的是一个地点的经纬度,然后直接传给前端渲染,那么肯定选用元组更合适

  2. 如果存储的数据或数量是可变的,比如社交平台上的一个日志功能,是统计一个用户在一周之内看了哪些用户的帖子,那么则用列表更合适

2.列表常用方法

列表数据类型支持很多方法,列表对象的所有方法所示如下:

  • list.append(x)

在列表末尾添加一个元素,相当于 a[len(a):] = [x]

  • list.extend(iterable)

用可迭代对象的元素扩展列表。相当于 a[len(a):] = iterable

  • list.insert(i, x)

在指定位置插入元素。第一个参数是插入元素的索引,因此,a.insert(0, x) 在列表开头插入元素, a.insert(len(a), x) 等同于 a.append(x)

  • list.remove(x)

从列表中删除第一个值为 x 的元素。未找到指定元素时,触发 ValueError 异常。

  • list.pop([i])

删除列表中指定位置的元素,并返回被删除的元素。未指定位置时,a.pop() 删除并返回列表的最后一个元素。(方法签名中 i 两边的方括号表示该参数是可选的,不是要求输入方括号。这种表示法常见于 Python 参考库)。

  • list.clear()

删除列表里的所有元素,相当于 del a[:]

  • list.index(x[, start[, end]])

返回列表中第一个值为 x 的元素的零基索引。未找到指定元素时,触发 ValueError 异常。可选参数 startend 是切片符号,用于将搜索限制为列表的特定子序列。返回的索引是相对于整个序列的开始计算的,而不是 start 参数。

  • list.count(x)

返回列表中元素 x 出现的次数。

  • list.sort(, key=None, reverse=False*)

就地排序列表中的元素(要了解自定义排序参数,详见 sorted())。

  • list.reverse()

翻转列表中的元素。

  • list.copy()

返回列表的浅拷贝。相当于 a[:]

多数列表方法示例:

>>> fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']
>>> fruits.count('apple')
2
>>> fruits.count('tangerine')
0
>>> fruits.index('banana')
3
>>> fruits.index('banana', 4)  # Find next banana starting a position 4
6
>>> fruits.reverse()
>>> fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']
>>> fruits.append('grape')
>>> fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']
>>> fruits.sort()
>>> fruits
['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']
>>> fruits.pop()
'pear'

insertremovesort 等方法只修改列表,不输出返回值——返回的默认值为 None1 这是所有 Python 可变数据结构的设计原则。

还有,不是所有数据都可以排序或比较。例如,[None, 'hello', 10] 就不可排序,因为整数不能与字符串对比,而 None 不能与其他类型对比。有些类型根本就没有定义顺序关系,例如,3+4j < 5+7j 这种对比操作就是无效的。

3.用列表实现堆栈

使用列表方法实现堆栈非常容易,最后插入的最先取出(“后进先出”)。把元素添加到堆栈的顶端,使用 append() 。从堆栈顶部取出元素,使用 pop() ,不用指定索引。例如:

>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack.append(7)
>>> stack
[3, 4, 5, 6, 7]
>>> stack.pop()
7
>>> stack
[3, 4, 5, 6]
>>> stack.pop()
6
>>> stack.pop()
5
>>> stack
[3, 4]

4.用列表实现队列

列表也可以用作队列,最先加入的元素,最先取出(“先进先出”);然而,列表作为队列的效率很低。因为,在列表末尾添加和删除元素非常快,但在列表开头插入或移除元素却很慢(因为所有其他元素都必须移动一位)。

实现队列最好用 collections.deque,可以快速从两端添加或删除元素。例如:

>>> from collections import deque
>>> queue = deque(["Eric", "John", "Michael"])
>>> queue.append("Terry")           # Terry arrives
>>> queue.append("Graham")          # Graham arrives
>>> queue.popleft()                 # The first to arrive now leaves
'Eric'
>>> queue.popleft()                 # The second to arrive now leaves
'John'
>>> queue                           # Remaining queue in order of arrival
deque(['Michael', 'Terry', 'Graham'])

5.列表推导式

列表推导式创建列表的方式更简洁。常见的用法为,对序列或可迭代对象中的每个元素应用某种操作,用生成的结果创建新的列表;或用满足特定条件的元素创建子序列。

例如,创建平方值的列表:

>>> squares = []
>>> for x in range(10):
...     squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

注意,这段代码创建(或覆盖)变量 x,该变量在循环结束后仍然存在。下述方法可以无副作用地计算平方列表:

squares = list(map(lambda x: x**2, range(10)))

或等价于:

squares = [x**2 for x in range(10)]

上面这种写法更简洁、易读。

列表推导式的方括号内包含以下内容:一个表达式,后面为一个 for 子句,然后,是零个或多个 forif 子句。结果是由表达式依据 forif 子句求值计算而得出一个新列表。 举例来说,以下列表推导式将两个列表中不相等的元素组合起来:

>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

等价于:

>>> combs = []
>>> for x in [1,2,3]:
...     for y in [3,1,4]:
...         if x != y:
...             combs.append((x, y))
...
>>> combs
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

注意,上面两段代码中,forif 的顺序相同。

表达式是元组(例如上例的 (x, y))时,必须加上括号:

>>> vec = [-4, -2, 0, 2, 4]
>>> # create a new list with the values doubled
>>> [x*2 for x in vec]
[-8, -4, 0, 4, 8]
>>> # filter the list to exclude negative numbers
>>> [x for x in vec if x >= 0]
[0, 2, 4]
>>> # apply a function to all the elements
>>> [abs(x) for x in vec]
[4, 2, 0, 2, 4]
>>> # call a method on each element
>>> freshfruit = ['  banana', '  loganberry ', 'passion fruit  ']
>>> [weapon.strip() for weapon in freshfruit]
['banana', 'loganberry', 'passion fruit']
>>> # create a list of 2-tuples like (number, square)
>>> [(x, x**2) for x in range(6)]
[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
>>> # the tuple must be parenthesized, otherwise an error is raised
>>> [x, x**2 for x in range(6)]
  File "<stdin>", line 1, in <module>
    [x, x**2 for x in range(6)]
               ^
SyntaxError: invalid syntax
>>> # flatten a list using a listcomp with two 'for'
>>> vec = [[1,2,3], [4,5,6], [7,8,9]]
>>> [num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

列表推导式可以使用复杂的表达式和嵌套函数:

>>> from math import pi
>>> [str(round(pi, i)) for i in range(1, 6)]
['3.1', '3.14', '3.142', '3.1416', '3.14159']

6.嵌套的列表推导式

列表推导式中的初始表达式可以是任何表达式,甚至可以是另一个列表推导式。

下面这个 3x4 矩阵,由 3 个长度为 4 的列表组成:

>>> matrix = [
...     [1, 2, 3, 4],
...     [5, 6, 7, 8],
...     [9, 10, 11, 12],
... ]

下面的列表推导式可以转置行列:

>>> [[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

如上节所示,嵌套的列表推导式基于其后的 for 求值,所以这个例子等价于:

>>> transposed = []
>>> for i in range(4):
...     transposed.append([row[i] for row in matrix])
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

反过来说,也等价于:

>>> transposed = []
>>> for i in range(4):
...     # the following 3 lines implement the nested listcomp
...     transposed_row = []
...     for row in matrix:
...         transposed_row.append(row[i])
...     transposed.append(transposed_row)
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

实际应用中,最好用内置函数替代复杂的流程语句。此时,zip() 函数更好用:

>>> list(zip(*matrix))
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

7.del 语句

del 语句按索引,而不是值从列表中移除元素。与返回值的 pop() 方法不同, del 语句也可以从列表中移除切片,或清空整个列表(之前是将空列表赋值给切片)。 例如:

>>>

>>> a = [-1, 1, 66.25, 333, 333, 1234.5]
>>> del a[0]
>>> a
[1, 66.25, 333, 333, 1234.5]
>>> del a[2:4]
>>> a
[1, 66.25, 1234.5]
>>> del a[:]
>>> a
[]

del 也可以用来删除整个变量:

>>> del a

此后,再引用 a 就会报错(直到为它赋与另一个值)。后文会介绍 del 的其他用法。

8.元组

元组由多个用逗号隔开的值组成,例如:

>>> t = 12345, 54321, 'hello!'
>>> t[0]
12345
>>> t
(12345, 54321, 'hello!')
>>> # Tuples may be nested:
... u = t, (1, 2, 3, 4, 5)
>>> u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
>>> # Tuples are immutable:
... t[0] = 88888
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> # but they can contain mutable objects:
... v = ([1, 2, 3], [3, 2, 1])
>>> v
([1, 2, 3], [3, 2, 1])

输出时,元组都要由圆括号标注,这样才能正确地解释嵌套元组。输入时,圆括号可有可无,不过经常是必须的(如果元组是更大的表达式的一部分)。不允许为元组中的单个元素赋值,当然,可以创建含列表等可变对象的元组。

虽然,元组与列表很像,但使用场景不同,用途也不同。元组是 immutable (不可变的),一般可包含异质元素序列,通过解包(见本节下文)或索引访问(如果是 namedtuples,可以属性访问)。列表是 mutable (可变的),列表元素一般为同质类型,可迭代访问。

构造 0 个或 1 个元素的元组比较特殊:为了适应这种情况,对句法有一些额外的改变。用一对空圆括号就可以创建空元组;只有一个元素的元组可以通过在这个元素后添加逗号来构建(圆括号里只有一个值的话不够明确)。丑陋,但是有效。例如:

>>> empty = ()
>>> singleton = 'hello',    # <-- note trailing comma
>>> len(empty)
0
>>> len(singleton)
1
>>> singleton
('hello',)

语句 t = 12345, 54321, 'hello!'元组打包 的例子:值 12345, 54321'hello!' 一起被打包进元组。逆操作也可以:

>>> x, y, z = t

称之为 序列解包 也是妥妥的,适用于右侧的任何序列。序列解包时,左侧变量与右侧序列元素的数量应相等。注意,多重赋值其实只是元组打包和序列解包的组合。