你有没有遇到过这种情况:
# 写了个简单的去重功能
items = [1, 2, 3, 2, 4, 3, 5]
unique_items = []
for item in items:
if item not in unique_items: # 你猜这个循环跑了多少次?
unique_items.append(item)
运行后发现数据量一大,程序慢得像乌龟爬行。然后你百度了一圈,才知道Python有个叫set的东西...
别笑,这种坑我踩过,你可能也踩过。
今天咱们就来彻底搞懂Python的四大金刚:列表、元组、字典、集合。
先搞懂一个灵魂问题:为什么需要这么多数据结构?
想象你在整理衣柜:
- 列表(list):就像挂衣服的杆子,可以随便加衣服、取衣服,还能调整顺序
- 元组(tuple):就像打包好的行李箱,装好后就不能改了,但跑得快
- 字典(dict):就像带标签的抽屉,按标签找东西超快
- 集合(set):就像去重神器,自动帮你去掉重复的东西
不同的场景用不同的工具,这才是编程的艺术!
列表(List):最灵活但也最容易踩坑
列表是Python中最常用的数据结构,但也是最容易写出低效代码的地方。
坑1:频繁的append操作
# ❌ 很多人这么写
result = []
data = range(100000)
for i in data:
result.append(i * 2) # 每次append都可能触发内存重分配
# ✅ 大神这么写
data = range(100000)
result = [i * 2 for i in data] # 一次性分配内存,速度快几倍
为什么差异这么大?列表的append操作有个"秘密":
当列表空间不够时,Python会重新分配一块更大的内存,然后把旧数据复制过去。数据量大的时候,这个过程就很耗时。
坑2:用in操作符找元素
# ❌ O(n)时间复杂度,数据大了就是灾难
users = ['alice', 'bob', 'charlie', 'david', 'eve']
if 'zebra' in users: # 最坏情况下要遍历整个列表
print('找到了')
# ✅ 如果需要频繁查找,考虑用set
user_set = {'alice', 'bob', 'charlie', 'david', 'eve'}
if 'zebra' in user_set: # O(1)时间复杂度
print('找到了')
记住一个简单的原则:列表适合增删改,不适合频繁查找。
坑3:修改正在遍历的列表
# ❌ 这会跳过一些元素
numbers = [1, 2, 3, 4, 5, 6]
for num in numbers:
if num % 2 == 0:
numbers.remove(num) # 删除元素后,后面的元素会前移
# 结果:[1, 3, 5] 期望:[1, 3, 5]?好像对?
# 但如果改成删除奇数...
for num in numbers:
if num % 2 == 1:
numbers.remove(num)
# 结果:[2, 4, 6] 还是 [2, 6]?答案是后者!
# ✅ 正确姿势:创建新列表或用列表推导
numbers = [1, 2, 3, 4, 5, 6]
odd_numbers = [num for num in numbers if num % 2 == 0]
元组(Tuple):不可变就是它的超能力
很多人觉得元组就是"不能修改的列表",这个理解太片面了。
为什么需要不可变?
想象一下:
# 元组可以作为字典的键
location_cache = {
(39.9042, 116.4074): "北京",
(31.2304, 121.4737): "上海"
}
# 列表就不行
# location_cache = {
# [39.9042, 116.4074]: "北京" # TypeError: unhashable type: 'list'
# }
不可变=可哈希=可以作为字典的键,这才是元组的真正价值!
元组的性能优势
import timeit
# 创建100万元素的列表和元组
list_time = timeit.timeit('[1] * 1000000', number=100)
tuple_time = timeit.timeit('(1,) * 1000000', number=100)
print(f'列表创建时间: {list_time:.4f}s')
print(f'元组创建时间: {tuple_time:.4f}s')
你会发现元组的创建和访问速度都比列表快,因为Python不需要为元组预留扩展空间。
元组的"可变"陷阱
# ❌ 看起来不可变,但里面的东西可能变
t = (1, [2, 3], 4)
t[1].append(5) # 这居然可以!
print(t) # (1, [2, 3, 5], 4)
# 解释:元组不可变的是引用,不是引用指向的对象
# 这就像你家的地址不能变,但家里可以装修
字典(Dict):查找的艺术
字典是Python中最强大的数据结构之一,但很多开发者只用到了它的皮毛。
坑1:KeyError的恐惧
# ❌ 每次都要检查key存在
user_scores = {'alice': 95, 'bob': 88}
score = user_scores['charlie'] if 'charlie' in user_scores else 0
# ✅ 用get方法更优雅
score = user_scores.get('charlie', 0) # 找不到就返回默认值
# ✅ 更高级:用defaultdict
from collections import defaultdict
word_count = defaultdict(int) # 默认值为0
word_count['hello'] += 1 # 不用先检查key存在
坑2:遍历字典的低效姿势
# ❌ 先遍历key再取value
user_data = {'name': 'Alice', 'age': 25, 'city': 'Beijing'}
for key in user_data.keys():
print(key, user_data[key]) # 两次查找
# ✅ 直接遍历items
for key, value in user_data.items(): # 一次遍历获取所有
print(key, value)
字典的高级玩法
# 字典合并(Python 3.9+)
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
merged = dict1 | dict2 # {'a': 1, 'b': 3, 'c': 4}
# 字典推导式
squares = {x: x**2 for x in range(5)} # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# 嵌套字典访问
data = {'user': {'profile': {'name': 'Alice'}}}
name = data.get('user', {}).get('profile', {}).get('name', 'Unknown')
集合(Set):去重和交并差的王者
集合是Python中最被低估的数据结构,很多人甚至不知道它的存在。
集合的超能力:去重
# ❌ 用列表去重,代码又长又慢
items = [1, 2, 3, 2, 4, 3, 5, 1, 6]
unique_items = []
for item in items:
if item not in unique_items: # O(n)查找
unique_items.append(item)
# 时间复杂度:O(n²)
# ✅ 用集合一行搞定
unique_items = list(set(items)) # O(n)
这性能差异在数据量大的时候是天壤之别!
集合的数学运算
set_a = {1, 2, 3, 4, 5}
set_b = {4, 5, 6, 7, 8}
# 交集:共同元素
common = set_a & set_b # {4, 5}
# 并集:所有元素
all_items = set_a | set_b # {1, 2, 3, 4, 5, 6, 7, 8}
# 差集:a有b没有
only_in_a = set_a - set_b # {1, 2, 3}
# 对称差集:只在其中一个集合的元素
exclusive = set_a ^ set_b # {1, 2, 3, 6, 7, 8}
这些操作在处理数据分析、权限控制等场景时特别好用。
集合的限制:元素必须可哈希
# ❌ 这会报错
invalid_set = {[1, 2], [3, 4]} # TypeError: unhashable type: 'list'
# ✅ 正确做法
valid_set = {(1, 2), (3, 4)} # 元组是不可变的,可以放入集合
实战场景选择指南
记不住这么多规则?记住这个选择指南:
数据量大且需要频繁查找
# 用户黑名单检查
blacklist = {'user123', 'user456', 'user789'} # 用set
if user_id in blacklist: # O(1)查找
reject_access()
数据需要保持不变性
# 配置参数
CONFIG = ('prod', 'mysql', 'localhost', 3306) # 用tuple
# 防止意外修改,还可以作为字典的键
需要快速通过键值查找
# 用户信息映射
user_info = {
'user123': {'name': 'Alice', 'age': 25},
'user456': {'name': 'Bob', 'age': 30}
} # 用dict
需要灵活增删改查
# 动态任务列表
tasks = ['task1', 'task2', 'task3'] # 用list
tasks.append('task4')
tasks.remove('task1')
tasks[2] = 'updated_task3'
性能对比:眼见为实
import timeit
import random
# 准备测试数据
data = list(range(100000))
search_target = 99999
# 列表查找
list_time = timeit.timeit(
lambda: search_target in data,
number=1000
)
# 集合查找
data_set = set(data)
set_time = timeit.timeit(
lambda: search_target in data_set,
number=1000
)
print(f'列表查找时间: {list_time:.4f}s')
print(f'集合查找时间: {set_time:.4f}s')
print(f'性能差异: {list_time/set_time:.1f}倍')
在我的机器上,集合查找比列表快了1000倍以上!
总结:选择数据结构的三部曲
记住这个简单的决策流程:
-
需要查找吗?
- 是 → 2
- 否 → 4
-
需要关联键值对吗?
- 是 → 用
dict - 否 → 用
set
- 是 → 用
-
数据需要修改吗?
- 是 → 4
- 否 → 用
tuple
-
用
list
一句话总结:
- 列表是"万金油",但不是"最优解"
- 元组是"保护伞",适合不变的配置
- 字典是"查表神器",键值对的首选
- 集合是"去重神器",数学运算的利器
下次写代码的时候,停下来想一想:我真的要用list吗?也许有更好的选择。
毕竟,选择合适的数据结构,比学会复杂的算法更重要。
你在项目里踩过哪些数据结构的坑?评论区聊聊,看看有没有比我更惨的案例~