元组和集合
你已经了解了列表。Python 还有两种集合类型可以解决列表无法解决的问题。元组保存一组固定的值,这些值永远不会改变。集合只保存唯一的值,并且无论集合多大,都能让你立即检查成员关系。
元组
元组是一组有序的值,创建后无法更改。圆括号用于定义元组,但它们是可选的。真正使其成为元组的是逗号。单元素元组需要一个尾随逗号。
point = (10, 20)
rgb = (255, 128, 0)
dimensions = (1920, 1080)
single = (42,) # 单元素元组需要尾随逗号
also_tuple = 42, 99 # 圆括号可选;逗号才使其成为元组通过索引访问的方式与列表完全相同。尝试更改元素会引发 TypeError:
point = (10, 20)
point[0] # 10
point[1] # 20
point[-1] # 20
point[0] = 99 # TypeError: 'tuple' object does not support item assignment何时使用元组
当你有一小组相关的值,它们属于一个整体且不会改变时,使用元组。坐标 (x, y)、颜色 (r, g, b)、姓名-分数对 ("小明", 87)。这种固定结构向阅读代码的人传达了这组值被视为一个单一单元。
locations = {}
locations[(40, -74)] = "New York" # 元组作为字典键,有效
locations[[40, -74]] = "New York" # 列表作为字典键,TypeError解包
解包将元组中的值取出,并在一行中将每个值赋给对应的名称。名称的数量必须与值的数量匹配。使用 * 将剩余元素捕获到一个列表中。
point = (10, 20)
x, y = point
print(x) # 10
print(y) # 20
first, *rest = [1, 2, 3, 4, 5]
# first = 1, rest = [2, 3, 4, 5]
head, *middle, tail = [1, 2, 3, 4, 5]
# head = 1, middle = [2, 3, 4], tail = 5命名元组
命名元组是每个位置都有名称的元组。无需记住 point[0] 是 x 坐标,你可以写 point.x。值仍然是不可变的;只是你获得了可读的属性名称而不是数字位置。
命名元组的导入
namedtuple 在 Python 的标准库中,但需要导入。from collections import namedtuple 是本课程中的第一个导入。导入将在模块章节中完整讲解。
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
Player = namedtuple("Player", ["name", "score", "level"])
p = Point(10, 20)
p.x # 10
p.y # 20
alice = Player("小明", 87, 5)
alice.name # "小明"
alice.score # 87集合
集合是一组没有保证顺序的唯一值。两次添加相同的值不会有任何效果:集合只保留每个元素的一个副本。使用花括号创建带元素的集合,或使用 set() 创建空集合。
tags = {"python", "beginner", "tutorial"}
numbers = {1, 2, 3, 4, 5}
empty = set() # 不是 {}(那是空字典)两次添加相同的值不会改变集合:
tags.add("python") # tags 未变,"python" 已经在其中何时使用集合
集合适用于三种场景:从列表中删除重复项、快速检查某物是否在大型集合中,以及比较两组以找出它们的共同点或差异。
# 从列表中删除重复项
raw = ["cat", "dog", "cat", "bird", "dog", "cat"]
unique = list(set(raw)) # ["cat", "dog", "bird"](顺序不保证)# 快速成员检查
valid_codes = {"USD", "EUR", "GBP", "JPY"}
code = "EUR"
if code in valid_codes: # 即使有数千个代码,也是即时查找
print("Valid")集合操作
集合支持你在数学中学过的相同操作:并集(任一集合中的所有元素)、交集(两个集合共有的元素)和差集(一个集合中有而另一个没有的元素)。Python 使用运算符符号表示这些操作,每种操作都有等价的方法形式。
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
a | b # {1, 2, 3, 4, 5, 6} (并集:任一集合中的元素)
a & b # {3, 4} (交集:两个集合都有的元素)
a - b # {1, 2} (差集:在 a 中但不在 b 中)
b - a # {5, 6} (反向差集)
a ^ b # {1, 2, 5, 6} (对称差集:仅在其中之一)这些也有方法形式:.union()、.intersection()、.difference()、.symmetric_difference()。
修改集合
集合是可变的。.add() 添加一个元素。.update() 从任何列表或其他可迭代对象一次添加多个元素。.remove() 删除一个元素,但如果不存在则引发错误。.discard() 如果元素存在则静默删除,不存在则什么都不做。
tags = {"python", "beginner"}
tags.add("tutorial") # 添加一个元素
tags.update(["web", "api"]) # 从任何可迭代对象添加多个元素
tags.remove("beginner") # 删除,如果未找到则引发 KeyError
tags.discard("missing") # 删除,未找到时无错误
tags.pop() # 删除并返回任意元素
tags.clear() # 删除所有元素当你不确定元素是否存在时,使用 .discard()。
冻结集合
冻结集合是创建后无法修改的集合。使用它的主要原因是:冻结集合是可哈希的,所以可以用作字典键或存储在其他集合中。
valid_statuses = frozenset({"active", "paused", "deleted"})
valid_statuses.add("archived") # AttributeError,frozenset 是不可变的选择合适的集合类型
四种类型,每种都有明确的角色。问问你需要对数据做什么,正确的选择通常就会变得明显。
| list | tuple | set | dict | |
|---|---|---|---|---|
| 有序 | 是 | 是 | 否 | 是(插入顺序) |
| 可变 | 是 | 否 | 是 | 是 |
| 允许重复 | 是 | 是 | 否 | 否(键) |
| 访问方式 | 索引 | 索引 | 不适用 | 键 |
| 使用场景 | 有序、可变的序列 | 固定记录 | 唯一值、快速成员测试 | 键值查找 |
快速决策规则:
- 需要按名称查找? → dict
- 需要可修改的有序集合? → list
- 有一组固定的相关值? → tuple
- 需要唯一值或快速成员测试? → set
实际应用
使用元组存储固定记录,使用集合跟踪唯一值:
home = (39.9042, 116.4074) # 纬度,经度
office = (39.9155, 116.4322)
home_lat, home_lon = home
print(f"Home: {home_lat}, {home_lon}")
# 使用集合跟踪唯一访客
visitors = set()
visitors.add("小明")
visitors.add("小红")
visitors.add("小明") # 已在集合中,静默忽略
visitors.add("小华")
print(f"Unique visitors: {len(visitors)}")
print(f"小明 visited: {'小明' in visitors}")
print(f"小李 visited: {'小李' in visitors}")
