gao

导航

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)

posted on 2026-05-14 15:26  code0101  阅读(0)  评论(0)    收藏  举报