Python3 数据结构:选对容器,事半功倍
我是陈默,一个正拼命上岸的码农。
搬家的时候,你用什么装东西?
衣服用箱子,碗碟用泡沫盒,零碎东西用收纳袋。
不同的东西用不同的容器。编程也一样——不同的数据,用不同的数据结构。
Python 提供了四种内置容器:列表、元组、字典、集合。每一种都有自己的脾气。选错了,代码又慢又丑。选对了,事半功倍。
今天我们来做一个横向对比,帮你彻底搞清楚什么时候用什么。
1. 四种数据结构,一眼看懂
# 列表(List):有序,可改,可重复
my_list = [1, 2, 3, 2, 4]
# 元组(Tuple):有序,不可改,可重复
my_tuple = (1, 2, 3, 2, 4)
# 字典(Dict):键值对,键唯一
my_dict = {"name": "陈默", "age": 25}
# 集合(Set):无序,不可重复
my_set = {1, 2, 3, 4}
一句话总结:
2. 横向对比:关键特性
注意: 集合虽然用 {},但空集合必须用 set(),因为 {} 是空字典。
3. 性能对比:快慢差很多
import time
data = list(range(100000))
# 列表查找:从头到尾扫一遍
start = time.time()
print(99999in data) # True
print(f"列表查找:{time.time() - start:.4f}秒")
# 集合查找:直接定位
data_set = set(data)
start = time.time()
print(99999in data_set) # True
print(f"集合查找:{time.time() - start:.6f}秒")
判断元素是否存在:集合是 O(1),列表是 O(n)。数据量越大,差距越大。
简单说:
4. 什么时候用列表?
能改、有顺序、允许重复。
# 待办事项
todos = ["写周报", "修 bug", "开会", "写周报"]
# 添加新任务
todos.append("学 Python")
# 完成一个
todos.pop(0)
print(todos) # 输出: ['修 bug', '开会', '写周报', '学 Python']
典型场景:
5. 什么时候用元组?
不用改、保护数据、作为字典的键。
# 地理坐标(不该被修改)
beijing = (39.9, 116.4)
shanghai = (31.2, 121.5)
# 用坐标当字典的键
distance = {
(beijing, shanghai): 1213,
(beijing, (39.1, 117.2)): 120,
}
print(f"北京到上海:{distance[(beijing, shanghai)]}公里")
# 输出: 北京到上海:1213公里
典型场景:
元组比列表快一点点,因为不可变,Python 不需要为修改预留空间。
6. 什么时候用字典?
按键查找、结构化数据、配置信息。
# 用户信息
user = {
"name": "陈默",
"age": 25,
"skills": ["Python", "SQL"],
"active": True,
}
# 快速查找
print(user["name"]) # 输出: 陈默
print("skills"in user) # 输出: True
典型场景:
# 计数器:统计每个字符出现的次数
text = "hello world"
counter = {}
for char in text:
if char != " ":
counter[char] = counter.get(char, 0) + 1
print(counter)
# 输出: {'h': 1, 'e': 1, 'l': 3, 'o': 2, 'w': 1, 'r': 1, 'd': 1}
7. 什么时候用集合?
去重、交集并集、快速判断存在。
# 去重:统计独立访客
visits = ["user1", "user2", "user1", "user3", "user2"]
unique = set(visits)
print(f"独立用户:{len(unique)}个") # 输出: 独立用户:3个
# 交集:共同好友
my_friends = {"小明", "小红", "小华", "小刚"}
your_friends = {"小红", "小李", "小华"}
common = my_friends & your_friends
print(f"共同好友:{common}") # 输出: 共同好友:{'小红', '小华'}
# 差集:我独有的
only_mine = my_friends - your_friends
print(f"只有我认识的:{only_mine}") # 输出: 只有我认识的:{'小明', '小刚'}
典型场景:
8. 互相转换
四种数据结构可以互相转换。
data = [1, 2, 3, 2, 1, 4]
# 列表 → 集合(去重)
unique = set(data)
print(unique) # 输出: {1, 2, 3, 4}
# 集合 → 列表(恢复顺序能力)
back_to_list = list(unique)
print(back_to_list) # 输出: [1, 2, 3, 4]
# 列表 → 元组(锁定数据)
locked = tuple(data)
print(locked) # 输出: (1, 2, 3, 2, 1, 4)
# 两个列表 → 字典
keys = ["name", "age", "job"]
values = ["陈默", 25, "程序员"]
person = dict(zip(keys, values))
print(person) # 输出: {'name': '陈默', 'age': 25, 'job': '程序员'}
# 字典的键/值 → 列表
print(list(person.keys())) # 输出: ['name', 'age', 'job']
print(list(person.values())) # 输出: ['陈默', 25, '程序员']
9. 嵌套组合:真实世界的数据
实际开发中,数据结构往往嵌套使用。
# 列表里套字典:常见的数据格式
students = [
{"name": "陈默", "scores": {"Python": 90, "SQL": 85}},
{"name": "小明", "scores": {"Python": 78, "SQL": 92}},
{"name": "小红", "scores": {"Python": 95, "SQL": 88}},
]
# 找出 Python 成绩最高的人
best = max(students, key=lambda s: s["scores"]["Python"])
print(f"Python 最高分:{best['name']}({best['scores']['Python']}分)")
# 输出: Python 最高分:小红(95分)
# 所有人名
names = [s["name"] for s in students]
print(names) # 输出: ['陈默', '小明', '小红']
# 字典里套列表:标签系统
articles = {
"Python入门": ["编程", "Python", "教程"],
"数据结构": ["编程", "Python", "基础"],
"今天吃什么": ["生活", "美食"],
}
# 找所有和"编程"相关的文章
coding_articles = [title for title, tags in articles.items() if"编程"in tags]
print(coding_articles) # 输出: ['Python入门', '数据结构']
# 所有标签(去重)
all_tags = set()
for tags in articles.values():
all_tags.update(tags)
print(all_tags) # 输出: {'编程', 'Python', '教程', '基础', '生活', '美食'}
10. 选择指南:一张图搞定
你需要什么?
│
├─ 按位置存取,可以修改 → 列表 []
│
├─ 按位置存取,不能修改 → 元组 ()
│
├─ 按名字查找 → 字典 {k: v}
│
└─ 去重 / 判断存在 / 集合运算 → 集合 set()
还有两个冷门但好用的:
collections.defaultdict:字典取不存在的键不报错,返回默认值collections.Counter:专门用来计数的字典collections.OrderedDict:保证插入顺序的字典(3.7+ 普通 dict 已经有序了)
from collections import Counter, defaultdict
# Counter:一行统计
words = ["苹果", "香蕉", "苹果", "橙子", "香蕉", "苹果"]
print(Counter(words)) # 输出: Counter({'苹果': 3, '香蕉': 2, '橙子': 1})
# defaultdict:不怕 KeyError
graph = defaultdict(list)
graph["北京"].append("上海")
graph["北京"].append("广州")
print(dict(graph)) # 输出: {'北京': ['上海', '广州']}
最后
数据结构的选择,直接决定了代码的质量。
选错了会怎样?
- 用列表存 10 万个元素然后判断"在不在这个列表里"——慢到怀疑人生
- 用可变列表存不该改的配置——出了 bug 找不到原因
记住一个原则:用最简单的结构,满足你的需求。
不需要修改就用元组,需要查找就用字典或集合,其他情况用列表。
我的建议:
回顾一下你最近写的代码,看看有没有用错数据结构的地方。试着替换成更合适的,对比一下性能和可读性。
好的代码,从选对容器开始。
今天就到这里。
我是陈默,我们下期再见。
如果你觉得这篇文章有帮助,欢迎关注我。我会持续分享 Python 学习的干货。