上篇讲了列表和字典,你已经知道怎么存一堆东西了。但 Python 里还有两种数据结构,出场率也很高——元组(tuple) 和 集合(set)。简单说:
- 元组 = 不能改的列表,适合存"定下来就不变"的数据
- 集合 = 自动去重的无序集合,适合做数学上的集合运算
一、元组(tuple)——锁定它,别改
长什么样?
# 用圆括号,元素用逗号隔开t1 = (1, 2, 3)t2 = ("苹果", "香蕉", "橘子")t3 = (1, "hello", 3.14) # 可以混装不同类型
元组能干啥?
1. 元组跟列表一样,可以索引、切片、遍历
t = ("北京", "上海", "广州", "深圳")print(t[0]) # 北京print(t[1:3]) # ('上海', '广州')for city in t:print(city)
2. 但!不能修改元素
t = (1, 2, 3)t[0] = 100# 报错!TypeError: 'tuple' object does not support item assignment
这是元组和列表最大的区别。所以叫「不可变序列」。
什么时候用元组而不是列表?
函数的多个返回值——这个超常用
defget_user(): name = "张三" age = 28return name, age # 返回的其实就是元组user = get_user()print(user) # ('张三', 28)print(user[0]) # 张三
字典的键——列表不行,但元组可以(原因见下文)
# 用坐标做键locations = { (39.9, 116.4): "北京", (31.2, 121.4): "上海"}print(locations[(39.9, 116.4)]) # 北京
数据不该被意外修改——比如配置常量
# 一周七天,写死了别动WEEKDAYS = ("周一", "周二", "周三", "周四", "周五", "周六", "周日")
一个坑:单元素元组
not_tuple = (1) # 这是整数1,不是元组!is_tuple = (1,) # 加个逗号,这才是元组
第一天踩过的坑,现在还债。单元素元组一定要加逗号。
补充知识:为什么有些类型可以做字典的键,有些不行?
哪些可以,哪些不行?
原因:字典键必须"可哈希"
Python 字典底层是哈希表。存一个键值对时,Python 会计算键的哈希值来确定存放位置。
hash("hello") # 产生一个固定整数hash(42) # 产生一个固定整数hash((1, 2, 3)) # 产生一个固定整数hash([1, 2, 3]) # 报错!unhashable type: 'list'
不可变类型(字符串、数字、元组)的哈希值在整个生命周期中固定不变,所以能做键。
可变类型(列表、字典)的内容可以随时改,改了哈希值就变了,之前存的数据就找不到了——所以 Python 禁止它们当键。
d = {}d[[1, 2]] = "value"# 报错!列表不可哈希d[(1, 2)] = "value"# 正常工作
但元组里装了可变对象也不行:
hash((1, [2, 3])) # 报错!元组内含列表,整体不可哈希
二、集合(set)——去重小能手
长什么样?
# 用花括号,元素逗号隔开s1 = {1, 2, 3, 4, 5}s2 = {"a", "b", "c"}
注意——空集合不能用 {},那是空字典。
empty_set = set() # 空集合empty_dict = {} # 这是空字典
集合三大特性
| | |
|---|
| 无序 | | {3, 1, 2} |
| 唯一 | | {1, 2, 2, 3} |
| 可哈希 | | {[1,2]} |
集合最常用的两个操作
1. 去重
names = ["张三", "李四", "张三", "王五", "李四"]unique_names = set(names)print(unique_names) # {'张三', '李四', '王五'}
再转回列表也很简单:
names_list = list(set(names))
2. 集合运算
a = {1, 2, 3, 4, 5}b = {4, 5, 6, 7, 8}# 交集——两边都有的print(a & b) # {4, 5}print(a.intersection(b)) # {4, 5}# 并集——合并去重print(a | b) # {1, 2, 3, 4, 5, 6, 7, 8}# 差集——a有b没有的print(a - b) # {1, 2, 3}print(b - a) # {8, 6, 7}# 对称差集——去掉交集剩下的print(a ^ b) # {1, 2, 3, 6, 7, 8}
真实场景:两个群成员分析
group_a = {"张三", "李四", "王五", "赵六"}group_b = {"李四", "赵六", "钱七", "孙八"}# 同时在两个群的人common = group_a & group_bprint(f"同时在两个群: {common}")# 只在A群的人only_a = group_a - group_bprint(f"只在A群: {only_a}")# 合并所有成员(去重)all_members = group_a | group_bprint(f"总成员数: {len(all_members)}")
遍历集合
s = {"Python", "Java", "Go", "Rust"}for lang in s:print(lang)# 注意:顺序不确定!每次运行顺序可能不一样
补充知识:什么类型能做集合的元素?
集合的元素要求和字典键一样——必须是可哈希的不可变类型。
{1, 2, 3} # 正常{"a", "b"} # 正常{(1, 2), (3, 4)} # 正常(元组不可变){[1, 2], [3, 4]} # 报错!列表不可哈希{{1, 2}} # 报错!集合本身可变
如果你想装一个"不可变的集合",用 frozenset:
fs = frozenset([1, 2, 3]) # 不可变集合,可以放进集合里{frozenset([1, 2]), frozenset([3, 4])} # 正常
补充知识:元组里的可变元素
元组的直接元素不能改,但如果元素本身是可变对象——那它内部可以变。
t = ([1, 2], 3)t[0].append(100) # 可以!元组没变,是列表变了print(t) # ([1, 2, 100], 3)
元组存的是引用,不是值。
今日小结
- 元组:用
(),不能改,适合存固定数据、做函数返回值、当字典键 - 集合:用
{} 或 set(),自动去重,支持交集、并集、差集运算 - 字典键和集合元素都要求"可哈希"——不可变类型(字符串、数字、元组)可以,可变类型(列表、字典、集合)不行,底层原因是哈希表需要键的哈希值固定不变
- 空元组是
(),空集合是 set(),空字典是 {}
一句话记法:数据写死了不想改→用元组;数据要去重或算交集差集→用集合。
明天讲字符串操作,文本处理基本功,是写代码几乎每天都要用的东西。