Python字典与集合详解
在Python的数据类型家族中,字典(dict)和集合(set)是两种强大而高效的非序列集合类型。它们不仅在日常编程中使用频繁,而且在处理大量数据时能提供近乎常数时间的查找性能。本文将深入剖析这两种数据结构的特性、用法和最佳实践,帮助你更加得心应手地使用它们。
目录
字典:键值对的世界
字典是Python中最灵活的数据结构之一,本质上是键值对的无序集合。每个键必须是唯一的且必须是不可变类型(如字符串、数字或元组),而值则可以是任意Python对象。
创建与初始化
# 创建空字典的两种方式
empty_dict1 = {}
empty_dict2 = dict()
# 使用字面量语法创建
person = {"name": "Alice", "age": 30, "city": "New York"}
# 使用dict()构造函数
person2 = dict(name="Bob", age=25, city="Boston")
# 使用键值对列表创建
items = [("apple", 0.5), ("banana", 0.3), ("orange", 0.7)]
prices = dict(items)
# 使用字典推导式
squares = {x: x**2 for x in range(6)}
print(squares) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
访问与修改
person = {"name": "Alice", "age": 30, "city": "New York"}
# 访问值
print(person["name"]) # Alice
# 使用get()方法安全访问(不存在键时返回默认值)
print(person.get("email")) # None
print(person.get("email", "Not Available")) # Not Available
# 修改值
person["age"] = 31
print(person) # {'name': 'Alice', 'age': 31, 'city': 'New York'}
# 添加新键值对
person["email"] = "alice@example.com"
# 删除键值对的几种方式
del person["city"] # 删除指定键
age = person.pop("age") # 删除并返回值
last_item = person.popitem() # 删除并返回最后添加的键值对(Python 3.7+保证顺序)
print(age) # 31
print(last_item) # ('email', 'alice@example.com')
字典的基本操作
student = {"name": "John", "grades": [85, 90, 78]}
# 获取所有键
print(list(student.keys())) # ['name', 'grades']
# 获取所有值
print(list(student.values())) # ['John', [85, 90, 78]]
# 获取所有键值对
print(list(student.items())) # [('name', 'John'), ('grades', [85, 90, 78])]
# 合并字典
extra_info = {"age": 20, "major": "Computer Science"}
student.update(extra_info)
print(student) # {'name': 'John', 'grades': [85, 90, 78], 'age': 20, 'major': 'Computer Science'}
# 检查键是否存在
print("name" in student) # True
print("address" in student) # False
# 清空字典
student.clear()
print(student) # {}
嵌套字典
字典可以包含任何Python对象,包括其他字典:
# 用字典表示复杂结构
university = {
"name": "Tech University",
"departments": {
"CS": {
"professors": 15,
"students": 250
},
"Physics": {
"professors": 8,
"students": 120
}
},
"founded": 1985
}
# 访问嵌套值
cs_students = university["departments"]["CS"]["students"]
print(cs_students) # 250
# 修改嵌套值
university["departments"]["CS"]["students"] += 10
集合:唯一性的保证
集合是唯一元素的无序集合,主要用于成员测试、消除重复项和执行数学集合运算。集合中的元素必须是不可变类型。
创建与初始化
# 创建空集合(注意:{}创建的是空字典)
empty_set = set()
# 从列表创建集合(会去除重复项)
numbers = set([1, 2, 2, 3, 4, 4, 5])
print(numbers) # {1, 2, 3, 4, 5}
# 使用花括号语法
fruits = {"apple", "banana", "cherry", "apple"}
print(fruits) # {'banana', 'cherry', 'apple'}
# 集合推导式
evens = {x for x in range(10) if x % 2 == 0}
print(evens) # {0, 2, 4, 6, 8}
基本操作
fruits = {"apple", "banana", "cherry"}
# 添加元素
fruits.add("orange")
# 移除元素
fruits.remove("banana") # 如果元素不存在会引发KeyError
fruits.discard("kiwi") # 如果元素不存在不会引发错误
# 随机移除一个元素并返回
popped = fruits.pop()
print(popped) # 可能是集合中的任何元素
# 清空集合
fruits.clear()
集合运算
集合支持数学集合运算,这是其最强大的特性之一:
A = {1, 2, 3, 4, 5}
B = {4, 5, 6, 7, 8}
# 并集(所有元素)
union = A | B # 或 A.union(B)
print(union) # {1, 2, 3, 4, 5, 6, 7, 8}
# 交集(共有元素)
intersection = A & B # 或 A.intersection(B)
print(intersection) # {4, 5}
# 差集(A中存在但B中不存在的元素)
difference = A - B # 或 A.difference(B)
print(difference) # {1, 2, 3}
# 对称差集(A或B中存在但不同时存在于两者中的元素)
sym_diff = A ^ B # 或 A.symmetric_difference(B)
print(sym_diff) # {1, 2, 3, 6, 7, 8}
# 子集与超集关系测试
C = {1, 2}
print(C.issubset(A)) # True
print(A.issuperset(C)) # True
不可变集合:frozenset
普通集合是可变的,但有时我们需要不可变版本,例如作为字典的键:
# 创建不可变集合
fs = frozenset([1, 2, 3, 4])
# 作为字典键使用
groups = {
frozenset(["Alice", "Bob"]): "Group 1",
frozenset(["Charlie", "Dave"]): "Group 2"
}
# 查找
print(groups[frozenset(["Alice", "Bob"])]) # Group 1
# 不能修改frozenset
# fs.add(5) # AttributeError
底层原理与性能
哈希表原理
字典和集合都基于哈希表实现,这就是为什么它们能提供O(1)的平均查找时间:
import time
# 比较列表和集合查找性能
def find_in_list(n):
big_list = list(range(1000000))
start = time.time()
for _ in range(n):
999999 in big_list # O(n)复杂度
return time.time() - start
def find_in_set(n):
big_set = set(range(1000000))
start = time.time()
for _ in range(n):
999999 in big_set # O(1)复杂度
return time.time() - start
n = 100
list_time = find_in_list(n)
set_time = find_in_set(n)
print(f"列表查找时间: {list_time:.5f}秒")
print(f"集合查找时间: {set_time:.5f}秒")
print(f"性能比: {list_time/set_time:.0f}x")
运行这段代码,你会发现集合的查找速度比列表快数千倍!
字典的有序性(Python 3.7+)
从Python 3.7开始,字典保持键插入顺序:
# Python 3.7+中字典保持插入顺序
colors = {}
colors["red"] = "#FF0000"
colors["green"] = "#00FF00"
colors["blue"] = "#0000FF"
for color, code in colors.items():
print(f"{color}: {code}")
# 输出顺序将是: red, green, blue
Python 3.6中这也是事实,但只是实现细节,不是语言保证。
高级用法与技巧
字典默认值处理
# 常见模式:计数
text = "hello world hello python world python"
word_count = {}
# 不优雅的方式
for word in text.split():
if word not in word_count:
word_count[word] = 0
word_count[word] += 1
# 使用get()更优雅
word_count = {}
for word in text.split():
word_count[word] = word_count.get(word, 0) + 1
# 使用defaultdict最优雅
from collections import defaultdict
word_count = defaultdict(int) # 默认值为0
for word in text.split():
word_count[word] += 1
print(dict(word_count)) # {'hello': 2, 'world': 2, 'python': 2}
字典与集合推导式
字典和集合推导式提供了创建复杂结构的简洁方法:
# 字典推导式
prices = {"apple": 0.5, "banana": 0.3, "orange": 0.7, "grape": 1.2}
cheap_prices = {k: v for k, v in prices.items() if v < 0.6}
print(cheap_prices) # {'apple': 0.5, 'banana': 0.3}
# 嵌套字典推导式
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = {f"({i},{j})": val
for i, row in enumerate(matrix)
for j, val in enumerate(row)}
print(flattened) # {'(0,0)': 1, '(0,1)': 2, ...}
# 集合推导式
words = ["hello", "world", "python", "programming"]
long_words = {word for word in words if len(word) > 5}
print(long_words) # {'python', 'programming'}
嵌套结构与递归函数
处理深度嵌套的字典或集合时,递归函数特别有用:
def find_keys(d, target, path=None):
"""在嵌套字典中查找特定键,并返回路径列表"""
if path is None:
path = []
results = []
for k, v in d.items():
if k == target:
results.append(path + [k])
if isinstance(v, dict):
# 递归搜索
nested_results = find_keys(v, target, path + [k])
results.extend(nested_results)
return results
# 使用示例
data = {
"users": {
"alice": {"email": "alice@example.com"},
"settings": {
"email": "settings@system.com",
"admin": {"email": "admin@system.com"}
}
}
}
paths = find_keys(data, "email")
print(paths) # [['users', 'alice', 'email'], ['users', 'settings', 'email'], ['users', 'settings', 'admin', 'email']]
常见陷阱与解决方案
可变对象作为字典键
只有不可变对象(如字符串、数字、元组)可以用作字典键或集合元素:
# 这会失败
# bad_dict = {[1, 2]: "value"} # TypeError: unhashable type: 'list'
# 但这可以工作
good_dict = {(1, 2): "value"}
# 如果需要使用复杂对象作为键,可以通过实现__hash__和__eq__方法
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return hash((self.x, self.y))
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
points = {Point(0, 0): "Origin", Point(1, 1): "One-One"}
字典视图对象与迭代
字典的.keys(), .values()和.items()方法返回视图对象,它们会动态反映字典的变更:
scores = {"Alice": 85, "Bob": 90, "Charlie": 78}
students = scores.keys()
print(students) # dict_keys(['Alice', 'Bob', 'Charlie'])
# 视图会跟踪字典变化
scores["Dave"] = 92
print(students) # dict_keys(['Alice', 'Bob', 'Charlie', 'Dave'])
# 视图支持集合操作
alice_bob = {"Alice", "Bob"}
print(set(students) & alice_bob) # {'Bob', 'Alice'}
# 避免在迭代时修改字典
for name in list(scores): # 转换为列表创建快照
if name.startswith("A"):
del scores[name]
集合元素的限制
集合只能包含可哈希(不可变)元素。常见的可哈希类型有:
- 整数、浮点数、复数
- 字符串
- 元组(如果元组所有元素都是可哈希的)
- frozenset
# 这会失败
# bad_set = {[1, 2], (3, 4)} # TypeError
# 这可以工作
good_set = {(1, 2), frozenset([3, 4])}
实际应用场景
字典的实际应用
- 数据转换和映射
country_codes = {
"USA": "1",
"UK": "44",
"China": "86",
"India": "91"
}
- 缓存/记忆化
# 斐波那契数列带缓存
def fibonacci(n, cache={}):
if n in cache:
return cache[n]
if n <= 1:
return n
result = fibonacci(n-1) + fibonacci(n-2)
cache[n] = result
return result
- 配置和设置存储
app_settings = {
"theme": "dark",
"notifications": True,
"language": "en",
"max_items": 50
}
集合的实际应用
- 去重
log_entries = ["ERROR: File not found",
"INFO: Task started",
"ERROR: File not found",
"INFO: Task completed"]
unique_entries = set(log_entries)
- 成员资格测试
valid_users = {"alice", "bob", "charlie", "dave"}
def authenticate(username):
return username.lower() in valid_users
- 数据比较
def find_differences(old_data, new_data):
old_set = set(old_data)
new_set = set(new_data)
added = new_set - old_set
removed = old_set - new_set
unchanged = old_set & new_set
return {
"added": added,
"removed": removed,
"unchanged": unchanged
}
总结
Python的字典和集合是两种功能强大的数据结构,它们共享类似的底层实现(哈希表),但服务于不同的目的:
-
字典 擅长键值对的存储和查找,非常适合表示映射关系、对象属性或任何需要通过键快速访问值的场景。
-
集合 擅长唯一元素的管理和集合运算,非常适合去重、成员测试和处理集合运算(如并集、交集等)的场景。
它们的主要优势在于查找速度快(近乎O(1)),使它们成为处理大量数据时的理想选择。
最佳实践要点:
- 使用字典存储映射关系和需要通过键快速查找的数据
- 使用集合去除重复项和执行快速成员资格测试
- 利用字典和集合推导式创建复杂结构
- 选择合适的工具处理默认值(get方法、setdefault方法或defaultdict)
- 记住它们对键/元素的限制(必须是可哈希/不可变的)
- 利用视图对象及其集合特性处理字典键
- 在Python 3.7+中,利用字典的有序性简化代码
通过深入理解和有效利用字典和集合,你可以编写出更高效、更简洁的Python代码,处理各种复杂的数据结构和算法问题。
无论是在数据处理、网络编程、Web开发还是机器学习等领域,掌握这两种数据结构都将使你的Python编程更上一层楼。