← 返回题目列表

Python 中 list、tuple、dict、set 有什么区别?

高频 简单 第 4 / 22 题 更新于 2026/07/25
Pythonlisttupledictset

简化版

list 是有序可变序列,适合按位置存放一组元素;tuple 是不可变序列,适合表达固定结构;dict 是键值映射,适合按 key 快速查值;set 是无重复集合,适合去重和集合运算。

详细版

四个基础容器对比:

类型是否有序是否可变是否允许重复典型用途
list有序可变允许动态列表、队列雏形、批量数据
tuple有序不可变允许固定记录、函数多返回值
dict保持插入顺序可变key 不重复配置、对象属性、映射关系
set不保证按业务顺序使用可变不允许去重、交并差运算、成员判断

例子:

names = ["Tom", "Jerry"]
point = (10, 20)
user = {"id": 1, "name": "Tom"}
tags = {"python", "backend", "interview"}

常见选择:

  • 要按下标访问、元素会变:选 list
  • 表达固定结构、不希望被改:选 tuple
  • 要按 key 查值:选 dict
  • 要去重、判断是否存在、做交集并集:选 set

完整版教学

一、先用四个问题选容器

Python 这四个容器不是互相替代的关系,而是面向不同访问方式。选型时先问四个问题:是否需要顺序,是否需要修改,是否需要 key-value 映射,是否需要去重或集合运算。把问题问对,比背“list 有序、dict 映射”更接近真实开发。

类型核心语义是否可变重复元素常见复杂度直觉
list有序序列允许下标访问 O(1),中间插删 O(n)
tuple固定序列/记录允许下标访问 O(1),不能原地改
dictkey -> value 映射key 不重复按 key 查找平均 O(1)
set无重复集合不允许成员判断平均 O(1)

这些复杂度是常见哈希表和动态数组实现下的平均认知,不代表每个操作都没有代价。比如 list.insert(0, x) 要移动后面的元素,10 万个元素头部插入一次就可能移动接近 10 万个引用;而 x in big_set 通常不需要线性扫描。

二、list:最常用的可变序列

list 适合保存一组有顺序的数据:

nums = [1, 2, 3]
nums.append(4)
nums[0] = 100

它的特点:

  • 支持下标访问;
  • 支持切片;
  • 支持增删改;
  • 保留元素顺序;
  • 可以存放不同类型对象。

常见操作:

nums.append(5)
nums.extend([6, 7])
nums.pop()
nums.sort()

面试补充:list 底层可以理解为动态数组思想,保存的是一段连续的对象引用。按下标访问很快,因为能直接计算位置;尾部 append 通常很快,因为列表会预留容量;但在中间插入或删除通常需要移动元素。比如长度为 10000 的列表,在下标 0 插入一个元素,后面约 10000 个引用都要后移。

插入前: [A, B, C, D]
insert(0, X)
插入后: [X, A, B, C, D]
          后面元素整体右移

所以 list 适合“有顺序、会变化、需要按位置访问”的数据;如果频繁在两端操作队列,应考虑 collections.deque,不要把 list 当万能队列。

记忆钩子:list 的强项是“按位置组织一串会变化的数据”,不是所有顺序数据都必须用 list,固定记录和双端队列都有更合适的工具。

三、tuple:固定结构更合适

tuple 是不可变序列:

point = (3, 4)
x, y = point

它常用于:

  • 函数返回多个值;
  • 表示固定结构,例如坐标、日期片段;
  • 作为 dict key 的一部分,前提是内部元素也可哈希;
  • 避免调用方误修改。

注意:

t = ([1, 2], 3)
t[0].append(4)
print(t)  # ([1, 2, 4], 3)

tuple 自身不可变,但它引用的可变对象内容仍可能改变。准确说,tuple 的“槽位绑定”不能变:第 0 位仍然指向原来的列表对象;只是那个列表对象内部发生了变化。这个细节在“tuple 能否作为 dict key”时尤其重要:(1, 2) 可哈希,([1], 2) 不可哈希,因为里面包含不可哈希的 list。

四、dict:按 key 查 value

dict 是 Python 里非常核心的数据结构:

user = {
    "id": 1,
    "name": "Tom",
}

print(user["name"])

它适合表达映射关系:

用户 id -> 用户对象
配置名 -> 配置值
单词 -> 出现次数

从 Python 3.7 开始,字典的插入顺序成为语言保证。因此遍历 dict 时会按插入顺序返回 key,但它的核心语义仍然是 key-value 映射,不要把它当成专门的排序结构。dict 的 key 必须可哈希且相等语义稳定,否则哈希表无法可靠定位桶位。

scores = {}
scores["Tom"] = 95
scores["Tom"] = 98
print(scores)  # {'Tom': 98}

这里不是保存了两个 Tom,而是同一个 key 的 value 被更新。用 dict 统计词频时,10 万个单词逐个扫描,平均每次更新是 O(1) 级别,比每次去列表里找某个单词的位置更合适。

五、set:去重和集合运算

set 的核心特点是元素不重复:

nums = [1, 2, 2, 3]
unique_nums = set(nums)
print(unique_nums)  # {1, 2, 3}

集合运算很适合面试题:

a = {"java", "python", "go"}
b = {"python", "rust"}

print(a & b)  # 交集 {'python'}
print(a | b)  # 并集
print(a - b)  # 差集

成员判断也很常用:

if user_id in allowed_ids:
    ...

allowed_ids 很大时,set 的成员判断通常比 list 线性扫描更合适。比如 100000 个 id 中判断某个 id 是否存在,list 最坏要比较 100000 次;set 通过哈希定位,平均不需要这么扫。set 不适合保存业务顺序,虽然迭代时会有某种顺序,但不应该把它当成稳定排序结果。

六、选型时看访问方式

可以按问题选容器:

  • 我需要按位置访问吗?listtuple
  • 数据会不会变?会变用 list,固定结构用 tuple
  • 我需要 key-value 吗?用 dict
  • 我只关心是否存在、是否重复吗?用 set
  • 我需要集合交并差吗?用 set
  • 我需要保留插入顺序并按 key 查吗?用 dict

还要注意“可变性”和“共享引用”会影响容器行为。list.copy()、切片、dict.copy() 都是浅拷贝,外层容器换了,里面的可变对象可能仍共享。容器里放的不是“值的深度副本”,而是对象引用,这能解释很多看起来诡异的联动修改。

users = [{"name": "Tom"}]
copied = users.copy()
copied[0]["age"] = 18
print(users)  # [{'name': 'Tom', 'age': 18}]

七、常见误区与追问

  • 误区:tuple 里面任何东西都不能变。 tuple 的槽位不可变,但槽位指向的可变对象内容仍可能被修改。
  • 误区:dict 有顺序后就可以替代 list。 dict 保持插入顺序,但核心是映射;按位置访问、切片、排序语义仍然不是它的主场。
  • 误区:set 是“自动排序去重”。 set 负责去重和集合运算,不保证按业务需要排序;需要排序要显式 sorted()
  • 追问:为什么 list 查找成员通常比 set 慢? list 要线性比较元素,set 通过哈希定位,平均查找更快,但要求元素可哈希。
  • 追问:dict 的 key 为什么不能用 list? list 可变且不可哈希,内容一变哈希位置就不稳定,dict 无法可靠查找。
  • 追问:函数多返回值为什么常说是 tuple? return a, b 实际返回一个 tuple,调用方可以用解包语法接收。

八、加强记忆

把四个容器记成“四种工具”:list 管有序且会变化的一串对象,tuple 管固定结构和不可变槽位,dict 管从 key 到 value 的快速映射,set 管去重、成员判断和交并差。面试回答时围绕访问方式、可变性、是否去重、是否需要 key 查找四条线展开,比只背定义更稳。