02-Python学习-字典集合、元组
Python 3 字典(dict)、集合(set)、元组(tuple)
这三个是 Python 中最常用的内置容器类型,理解它们的特性和适用场景是写好 Python 代码的基础。
一、元组(tuple)—— 不可变的序列
1.1 创建元组
元组用圆括号 () 表示,一旦创建就不能修改。
# 直接创建
point = (3, 4)
rgb = (255, 128, 0)
# 单元素元组必须加逗号(这是最常见的坑)
single = (42,) # 这是一个元组
not_tuple = (42) # 这只是一个整数 42
# 省略括号也可以
coords = 3, 4 # 等价于 (3, 4)
# 从其他可迭代对象转换
t = tuple([1, 2, 3]) # (1, 2, 3)
t = tuple("hello") # ('h', 'e', 'l', 'l', 'o')
t = tuple(range(5)) # (0, 1, 2, 3, 4)
# 空元组
empty = ()
1.2 基本操作
colors = ("red", "green", "blue", "green")
# 索引访问(从 0 开始)
colors[0] # 'red'
colors[-1] # 'blue'(最后一个元素)
# 切片
colors[1:3] # ('green', 'blue')
# 拼接(生成新元组,原元组不变)
colors + ("yellow",) # ('red', 'green', 'blue', 'green', 'yellow')
# 重复
("ha",) * 3 # ('ha', 'ha', 'ha')
# 成员判断
"red" in colors # True
# 长度
len(colors) # 4
# 计数和查找索引
colors.count("green") # 2
colors.index("blue") # 2
1.3 解包(unpacking)
这是元组最常用也最优雅的特性之一:
# 基本解包
point = (3, 4)
x, y = point
print(x, y) # 3 4
# 交换变量(不需要临时变量)
a, b = 10, 20
a, b = b, a
print(a, b) # 20 10
# 用 * 收集多余元素
first, *rest = (1, 2, 3, 4, 5)
print(first) # 1
print(rest) # [2, 3, 4, 5]
head, *middle, tail = (1, 2, 3, 4, 5)
print(head) # 1
print(middle) # [2, 3, 4]
print(tail) # 5
# 忽略某些值
_, y, _ = (1, 2, 3) # 只关心 y
1.4 为什么用元组
- 数据不可变,可以作为字典的键、集合的元素(列表不行)
- 语义明确,表示"这组数据是一个整体,不应该被修改",比如坐标
(x, y)、RGB 颜色值 - 比列表性能略好,因为不可变,Python 可以做内部优化
# 元组可以做字典的键
location = {}
location[(35.68, 139.69)] = "Tokyo"
location[(40.71, -74.01)] = "New York"
# 列表不行,会报 TypeError
# location[[35.68, 139.69]] = "Tokyo" # TypeError!
二、字典(dict)—— 键值对映射
2.1 创建字典
# 花括号直接创建
person = {"name": "Alice", "age": 30, "city": "Beijing"}
# dict() 构造函数
config = dict(host="localhost", port=8080, debug=True)
# 从键值对列表创建
items = [("a", 1), ("b", 2), ("c", 3)]
d = dict(items) # {'a': 1, 'b': 2, 'c': 3}
# 用 dict.fromkeys() 创建默认值字典
keys = ["name", "age", "email"]
profile = dict.fromkeys(keys, "unknown")
# {'name': 'unknown', 'age': 'unknown', 'email': 'unknown'}
# 空字典
empty = {}
2.2 增删改查
student = {"name": "Bob", "age": 20}
# ---- 读取 ----
student["name"] # 'Bob'
student.get("gpa") # None(键不存在返回 None)
student.get("gpa", 0.0) # 0.0(键不存在返回默认值)
student.get("name", "N/A") # 'Bob'(键存在,返回实际值)
# ---- 添加 / 修改 ----
student["gpa"] = 3.8 # 键不存在,添加
student["age"] = 21 # 键已存在,修改
# ---- 删除 ----
del student["gpa"] # 删除指定键
age = student.pop("age") # 删除并返回值,age 被赋值为 21
# ---- 批量更新 ----
student.update({"major": "CS", "year": 3})
student.update(city="Shanghai") # 也可以用关键字参数
2.3 遍历字典
scores = {"math": 95, "english": 88, "physics": 92}
# 遍历键
for subject in scores:
print(subject)
for subject in scores.keys():
print(subject)
# 遍历值
for score in scores.values():
print(score)
# 遍历键值对(最常用)
for subject, score in scores.items():
print(f"{subject}: {score}")
2.4 常用方法
d = {"a": 1, "b": 2, "c": 3}
len(d) # 3(键值对数量)
"a" in d # True(检查键是否存在)
1 in d # False(in 检查的是键,不是值)
d.copy() # 浅拷贝
d.setdefault("d", 0) # 如果键不存在就设置默认值并返回,存在则返回已有值
list(d.keys()) # ['a', 'b', 'c']
list(d.values()) # [1, 2, 3]
list(d.items()) # [('a', 1), ('b', 2), ('c', 3)]
# popitem():移除并返回最后插入的键值对
d.popitem() # ('c', 3)
2.5 实际应用示例
统计词频:
text = "apple banana apple cherry banana apple"
word_count = {}
for word in text.split():
word_count[word] = word_count.get(word, 0) + 1
# {'apple': 3, 'banana': 2, 'cherry': 1}
用 collections.Counter 更简洁地实现:
from collections import Counter
text = "apple banana apple cherry banana apple"
word_count = Counter(text.split())
# Counter({'apple': 3, 'banana': 2, 'cherry': 1})
分组数据:
students = [
{"name": "Alice", "grade": "A"},
{"name": "Bob", "grade": "B"},
{"name": "Charlie", "grade": "A"},
{"name": "Diana", "grade": "B"},
]
groups = {}
for s in students:
grade = s["grade"]
groups.setdefault(grade, []).append(s["name"])
# {'A': ['Alice', 'Charlie'], 'B': ['Bob', 'Diana']}
多条件查找(嵌套字典):
users = {
"alice": {"age": 30, "role": "admin"},
"bob": {"age": 25, "role": "user"},
"carol": {"age": 28, "role": "admin"},
}
# 找出所有管理员
admins = [name for name, info in users.items() if info["role"] == "admin"]
# ['alice', 'carol']
三、集合(set)—— 无序、不重复的元素集
3.1 创建集合
# 花括号创建(注意:空集合只能用 set())
fruits = {"apple", "banana", "cherry"}
# set() 从其他可迭代对象创建
nums = set([1, 2, 3, 2, 1]) # {1, 2, 3}(自动去重)
chars = set("hello") # {'h', 'e', 'l', 'o'}(去重 + 无序)
# 空集合(不能用 {},那是空字典)
empty = set()
# 集合推导式
squares = {x ** 2 for x in range(5)} # {0, 1, 4, 9, 16}
3.2 基本操作
s = {1, 2, 3}
# 添加元素
s.add(4) # {1, 2, 3, 4}
s.add(3) # {1, 2, 3, 4}(已存在,无变化)
# 删除元素
s.remove(2) # 不存在会抛出 KeyError
s.discard(99) # 不存在也不报错(推荐用这个)
popped = s.pop() # 移除并返回一个任意元素(无序的)
# 清空
s.clear() # set()
# 成员判断(O(1) 时间复杂度,非常快)
3 in {1, 2, 3} # True
3.3 集合运算
这是集合最强大的特性,可以直接用运算符做数学上的集合操作:
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
# 并集:两个集合的所有元素
a | b # {1, 2, 3, 4, 5, 6}
a.union(b) # 同上
# 交集:两个集合的共同元素
a & b # {3, 4}
a.intersection(b)
# 差集:在 a 中但不在 b 中
a - b # {1, 2}
a.difference(b)
# 对称差集:在 a 或 b 中,但不同时在两者中
a ^ b # {1, 2, 5, 6}
a.symmetric_difference(b)
# 子集 / 超集判断
{1, 2} <= {1, 2, 3} # True(子集)
{1, 2, 3} >= {1, 2} # True(超集)
{1, 2} < {1, 2, 3} # True(真子集)
{1, 2, 3}.issubset({1, 2, 3, 4}) # True
# 判断是否无交集
{1, 2}.isdisjoint({3, 4}) # True
3.4 原地集合运算
a = {1, 2, 3}
b = {2, 3, 4}
a |= {5, 6} # a 变成 {1, 2, 3, 5, 6},并集后更新自身
a &= b # a 变成 {2, 3},交集后更新自身
a -= {2} # a 变成 {3},差集后更新自身
3.5 实际应用示例
列表去重:
data = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
unique = list(set(data)) # [1, 2, 3, 4, 5, 6, 9](顺序不保证)
unique_ordered = list(dict.fromkeys(data)) # [3, 1, 4, 5, 9, 2, 6](保序去重)
快速判断元素是否在集合中:
# 用列表:O(n) 慢
valid_ids = [1001, 1002, 1003, ...]
if user_id in valid_ids: # 每次都要遍历整个列表
pass
# 用集合:O(1) 快
valid_ids = {1001, 1002, 1003, ...}
if user_id in valid_ids: # 瞬间判断
pass
找出两个数据源的差异:
yesterday = {"alice", "bob", "carol"}
today = {"bob", "carol", "diana"}
new_users = today - yesterday # {'diana'} 新增的用户
left_users = yesterday - today # {'alice'} 离开的用户
stayed = yesterday & today # {'bob', 'carol'} 留下的用户
四、frozenset —— 不可变的集合
frozenset 是集合的不可变版本,创建后不能添加或删除元素。因为不可变,所以可以作为字典的键或集合的元素:
fs = frozenset([1, 2, 3])
# 不能修改
# fs.add(4) # AttributeError!
# 可以做字典的键
graph = {
frozenset({"A", "B"}): 1.5,
frozenset({"B", "C"}): 2.3,
}
五、三者对比
| 特性 | 元组 tuple | 字典 dict | 集合 set |
|---|---|---|---|
| 语法 | (1, 2, 3) |
{"a": 1} |
{1, 2, 3} |
| 可变性 | 不可变 | 可变 | 可变 |
| 是否有序 | 有序(按位置) | 有序(Python 3.7+ 保持插入顺序) | 无序 |
| 是否允许重复 | 允许 | 键不允许重复 | 不允许 |
| 主要用途 | 固定的数据组合 | 键值映射 / 查找 | 去重 / 集合运算 / 快速判断成员 |
| 可做字典的键 | 可以 | 不可以 | 不可以(frozenset 可以) |
| 空值写法 | () |
{} |
set() |
六、何时用哪个
用元组当你需要:
- 表示一个固定结构的数据(坐标、颜色、数据库记录行)
- 作为字典的键
- 函数返回多个值
用字典当你需要:
- 通过名字(键)快速查找值
- 表示一个实体的多个属性
- 做缓存 / 计数 / 分组
用集合当你需要:
- 去重
- 判断某个元素是否在一组数据中(并且要快)
- 做交集、并集、差集等运算
七、常见陷阱
1. 用 {} 创建的不是空集合
type({}) # <class 'dict'>,不是 set!
type(set()) # <class 'set'>
2. 集合和字典的键必须是可哈希类型
# 可以:int、float、str、tuple(元素也必须可哈希)、bool、None
# 不可以:list、dict、set
bad_set = {[1, 2]} # TypeError: unhashable type: 'list'
bad_dict = {[1, 2]: 3} # TypeError: unhashable type: 'list'
3. 元组虽不可变,但如果元素是可变对象,那个元素本身仍可变
t = (1, [2, 3], 4)
t[1].append(99)
print(t) # (1, [2, 3, 99], 4)
# t[1] = [5, 6] # 这才是 TypeError,不能替换元素本身
4. 字典的浅拷贝不会复制嵌套对象
original = {"names": ["Alice", "Bob"]}
copy = original.copy()
copy["names"].append("Charlie")
print(original) # {'names': ['Alice', 'Bob', 'Charlie']} ← 也被改了!
# 需要深拷贝时:
import copy
deep = copy.deepcopy(original)
本文来自博客园,作者:code0101,转载请注明原文链接:https://www.cnblogs.com/cmct/p/20042677
浙公网安备 33010602011771号