☰
Python列表、集合、字典核心区别举例详解
2026/10/13 3:27:31 网站建设 项目流程

前言


列表(list)、集合(set)、字典(dict)是 Python 用得最多的三个内置容器,但很多人在 选的时候是凭手感:反正都能装东西,随便挑一个能跑就行。结果常见的是拿列表做成员判断, 数据一多就慢得离谱;或者拿集合去按顺序取值,一取就报not subscriptable。这三个容器 的差别不是风格问题,而是数据结构层面的取舍,选错了会直接体现在正确性和效率上。


本文按七个维度横向对比:有没有顺序、能不能重复、元素能不能被哈希、能不能按下标取值、 查找要多少代价、能不能改、典型用途是什么。每个维度都配可运行的例子,最后一节给一张总 表。


先确定版本基准:Python 2.7 已于 2020 年 1 月 1 日停止维护,本文使用 Python 3 的写法, 示例适用于 Python 3.8 及以上。文中的复杂度指CPython 实现下的代价,其他实现可能 不同;涉及「平均情况」的地方会注明。字典「有序」指的是 Python 3.7 起保证按插入顺序 迭代(3.6 只是 CPython 的实现细节,不要依赖)。


一、有序性:三者完全不同



  • 列表有序:元素按插入顺序排列,位置固定,可以按下标取值。

  • 集合无序:集合不记录插入顺序,迭代出来的顺序是实现细节,不要依赖。

  • 字典有序:3.7 起保证按插入顺序迭代;更新已存在的键不会改变它的位置。


# 适用于 Python 3.8+
lst = [3, 1, 3]
st = {3, 1, 3}
d = {"a": 1, "b": 2}

print(lst) # [3, 1, 3] 顺序就是插入顺序
print(sorted(st)) # [1, 3] 要确定顺序得自己排序
print(list(d)) # ['a', 'b'] 键按插入顺序

注意sorted(st)只是把集合转成排好序的列表,并没有让集合本身变得有序。集合永远 没有「第几个元素」这个概念。


二、可重复性:列表允许,集合和字典的键不允许


列表对重复元素毫无意见,长度是多少就是多少。集合的每个元素只能出现一次,字典的每个键 也只能出现一次(值可以重复,也可以不可哈希)。


# 适用于 Python 3.8+
print(len([1, 1, 2, 2, 2])) # 5,全留着
print(len({1, 1, 2, 2, 2})) # 2,重复的自动合并
d = {"a": 1, "b": 1}
print(len(d)) # 2,值相同没关系,键不同即可

「元素唯一」是集合最核心的性质,去重、求交并差都靠它。但也正因为唯一,往集合里放重复 值不会有任何提示,是静默合并——如果不小心的元素本来不该相等,问题会藏得很深。


三、可哈希性:集合元素和字典键的门槛


要放进集合、或者当字典的键,对象必须可哈希(hashable)。可哈希的对象在它的生命周期 内哈希值不变,并且能和其他对象比较相等。基本类型(整数、字符串、元组、frozenset) 都可哈希;列表、字典、集合这类可变对象都不可哈希。


# 适用于 Python 3.8+
s = {1, "a", (2, 3)} # 合法
# s = {[1, 2]} # TypeError: unhashable type: 'list'

d = {}
d[(1, 2)] = "tuple key" # 合法
# d[[1, 2]] = "x" # TypeError: unhashable type: 'list'

fs = frozenset([1, 2]) # frozenset 不可变,因此可哈希
d[fs] = "frozenset key"
print(sorted(d, key=str)) # 能同时容纳元组键和 frozenset 键

字典的值没有这个限制,列表、字典都能当值。这条差异经常被混淆:限制只针对键。


四、取值方式:下标 vs 键 vs 不能取


列表按下标取,字典按键取,集合两者都不行。


# 适用于 Python 3.8+
lst = [10, 20, 30]
print(lst[0]) # 10

d = {"x": 10, "y": 20}
print(d["x"]) # 10
print(d.get("z")) # None,键不存在时 get 返回 None,不抛异常

st = {10, 20, 30}
# print(st[0]) # TypeError: 'set' object is not subscriptable

集合要用「在不在」的方式访问:x in st。它不提供「第几个」这种位置概念,也没有__getitem__。


五、查找代价:这是最该记住的一条


成员判断的代价差别极大,官方文档给出的复杂度是:




操作复杂度



x in lst(列表成员判断)O(n)

x in st(集合成员判断)平均O(1),最坏O(n)

key in d(字典键判断)平均O(1),最坏O(n)

lst[i](列表按下标取)O(1)

d[key](字典按键取)平均O(1),最坏O(n)



# 适用于 Python 3.8+
big_list = list(range(100000))
big_set = set(range(100000))

print(99999 in big_list) # True,但要把列表扫一遍
print(99999 in big_set) # True,平均只需一次定位

上面两个表达式结果相同,代价却差着量级。判断「在不在」时优先用集合或字典的键。「最坏O(n)」指的是所有键都哈希到同一个值这种极端情况,正常散列下不会发生。


六、可变性:三者都能改,但方式不同


三个容器都是可变的,但修改的入口不一样。


# 适用于 Python 3.8+
lst = [1, 2]
lst.append(3) # 列表有 append/extend/insert/remove/pop/sort

st = {1, 2}
st.add(3) # 集合用 add / discard / remove / update
st.discard(1)

d = {"a": 1}
d["b"] = 2 # 字典按键赋值
d.update({"c": 3})
print(lst, st, d) # [1, 2, 3] {2, 3} {'a': 1, 'b': 2, 'c': 3}

列表有位置概念,所以有insert、pop(i)这类按下标的方法;集合没有位置,只有 「加一个 / 去掉一个 / 批量化」;字典则是「按键」存取。


七、典型用途




容器典型用途



列表有序记录、需要排序、需要按下标或切片访问、需要保留重复项

集合去重、成员判断、交并差运算、给「已见过」的东西做索引

字典按键查表、计数、分组、映射关系、需要给每个键挂一个值



# 适用于 Python 3.8+
# 列表:保序、允许重复
scores = [88, 92, 88]

# 集合:去重 + 快速判断
unique_ids = {101, 102, 103}
print(102 in unique_ids) # True

# 字典:计数
counts = {}
for c in "abracadabra":
counts[c] = counts.get(c, 0) + 1
print(counts["a"]) # 5

字典做计数是最经典的用法:键是元素,值是出现次数。如果只想统计「出现过哪些」,不需要 次数,用集合就够了。


八、核心区别总表




维度列表 list集合 set字典 dict



有序性有序,位置固定无序,顺序是细节3.7 起保证插入顺序

可重复允许元素唯一键唯一,值可重复

下标访问支持不支持不支持(按键访问)

切片支持不支持不支持

元素/键可否哈希无要求必须可哈希键必须可哈希,值无要求

成员判断O(n)平均O(1)平均O(1)

按下标/键取值O(1)不支持平均O(1)

可变是是是

可哈希(自身)否否否

常用方法append、sort、popadd、discard、unionget、keys、update

典型场景有序序列、排序去重、成员判断、交并差查表、计数、分组



常见坑点


坑点 1:拿列表做高频成员判断。


❌ 错误写法:


# 适用于 Python 3.8+
allowed = ["a", "b", "c", "d"]
for x in ["a", "z"]:
if x in allowed: # 每次都线性扫描
print(x)

✅ 正确写法:


# 适用于 Python 3.8+
allowed = {"a", "b", "c", "d"} # 用集合,成员判断平均是常数级
for x in ["a", "z"]:
if x in allowed:
print(x)

坑点 2:对集合按下标取值。


❌ 错误写法:


# 适用于 Python 3.8+
st = {10, 20}
# print(st[0]) # TypeError: 'set' object is not subscriptable

✅ 正确写法:


# 适用于 Python 3.8+
st = {10, 20}
print(sorted(st)[0]) # 10,需要位置就先转成有序序列

坑点 3:把{}当成空集合。


❌ 错误写法:


# 适用于 Python 3.8+
s = {}
print(type(s)) # <class 'dict'>,这是空字典

✅ 正确写法:


# 适用于 Python 3.8+
s = set()
print(type(s), len(s)) # <class 'set'> 0

空集合只能写set(),{}是空字典。


坑点 4:把不可哈希的对象当字典键或集合元素。


❌ 错误写法:


# 适用于 Python 3.8+
# d = {[1, 2]: "x"} # TypeError: unhashable type: 'list'
# s = {{1, 2}} # TypeError: unhashable type: 'set'

✅ 正确写法:


# 适用于 Python 3.8+
d = {(1, 2): "x"} # 元组做键
s = {frozenset({1, 2})} # frozenset 可哈希
print(d, len(s)) # {(1, 2): 'x'} 1

坑点 5:以为集合会保持插入顺序。


❌ 错误写法:


# 适用于 Python 3.8+
seen = {3, 1, 2}
# 依赖 print(seen) 的顺序做逻辑判断

✅ 正确写法:


# 适用于 Python 3.8+
seen = {3, 1, 2}
ordered = sorted(seen) # 需要顺序就显式排序
print(ordered) # [1, 2, 3]

坑点 6:遍历字典或集合时改大小。


❌ 错误写法:


# 适用于 Python 3.8+
d = {"a": 1, "b": 2, "c": 3}
# for k in d:
# if d[k] < 2:
# del d[k] # RuntimeError: dictionary changed size during iteration

✅ 正确写法:


# 适用于 Python 3.8+
d = {"a": 1, "b": 2, "c": 3}
for k in list(d): # 先固化一份键的快照再遍历
if d[k] < 2:
del d[k]
print(d) # {'b': 2, 'c': 3}

坑点 7:字典取值误用[]处理可能不存在的键。


❌ 错误写法:


# 适用于 Python 3.8+
d = {"a": 1}
# print(d["b"]) # KeyError: 'b'

✅ 正确写法:


# 适用于 Python 3.8+
d = {"a": 1}
print(d.get("b")) # None
print(d.get("b", 0)) # 0,给个默认值
if "b" in d:
print(d["b"])

坑点 8:往集合里塞「本该不同」的元素,结果被静默合并。


❌ 错误写法:


# 适用于 Python 3.8+
st = {1, True, 1.0}
print(len(st)) # 1,因为 1 == True == 1.0,哈希也相同

✅ 正确写法:


# 适用于 Python 3.8+
st = {1, True, 1.0}
# 需要区分类型就别用集合,或者先统一成唯一标识
ids = {"num:1", "bool:True", "float:1.0"}
print(len(ids)) # 3

集合靠「哈希相等 + 相等比较」判重,数值相等的不同类型会被当成同一个。


总结




你的需求选谁



要顺序、要下标、要保留重复列表

要判断「在不在」、要去重、要交并差集合

要按名字/编号查一个值、要计数分组字典

既要顺序又要快速查找字典的键,或列表 + 集合两手准备



一句话概括三者的分工:列表管顺序,集合管唯一和存在性,字典管映射。它们之间能互相 转换(list()/set()/dict()),但每次转换都要付出一次完整遍历的代价,而且会 丢掉各自不擅长承载的信息——比如列表转集合就永远丢了顺序。选择时先问清楚:这份数据 「顺序重要还是存在性重要」,答案基本就出来了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询