Python 的字典(dict)是开发中最常用的映射类型数据结构,基于哈希表实现,提供 O(1) 平均时间复杂度的键值存取。本文从八种创建方式入手,深入哈希表底层原理,讲解键的哈希性要求、Python 3.6+ 优化、性能对比以及实战中的构建模式,帮助你彻底掌握 dict 的正确用法。
一、什么是字典
字典是一种键值对存储结构,每个键映射到一个值。键必须是可哈希(不可变)类型,如字符串、整数、元组(元素全不可变)。Python 3.7+ 官方保证字典保持插入顺序。通过键直接访问值,速度极快。
- person = {
- 'name': '小明',
- 'age': 25,
- 'city': '北京',
- 'job': '工程师',
- }
- print(person['name']) # 小明
复制代码
二、字典的八种创建方式
1. 花括号字面量(最常用)- empty = {}
- person = {'name': '小明', 'age': 25, 'city': '北京'}
- # 嵌套字典
- config = {
- 'database': {'host': 'localhost', 'port': 5432},
- 'cache': {'host': 'redis://localhost', 'port': 6379},
- }
复制代码
2. dict() 构造函数- # 关键字参数:键必须是合法标识符
- d1 = dict(name='小明', age=25, city='北京')
- # 可迭代对象(每个元素是元组)
- d2 = dict([('name', '小明'), ('age', 25), ('city', '北京')])
- # zip 组合两个列表
- keys = ['name', 'age', 'city']
- values = ['小明', 25, '北京']
- d3 = dict(zip(keys, values))
- # 混合使用
- d4 = dict(name='小明', age=25)
- d4.update({'city': '北京'})
复制代码
3. 字典推导式- squares = {x: x ** 2 for x in range(6)}
- # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
- # 带条件
- even_squares = {x: x ** 2 for x in range(10) if x % 2 == 0}
- # {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}
- # 反转键值
- original = {'a': 1, 'b': 2, 'c': 3}
- reversed_dict = {v: k for k, v in original.items()}
- # 过滤
- scores = {'小明': 85, '小红': 92, '小刚': 78, '小李': 95}
- passing = {name: score for name, score in scores.items() if score >= 90}
复制代码
4. fromkeys()- keys = ['a', 'b', 'c', 'd']
- d = dict.fromkeys(keys, 0) # {'a': 0, 'b': 0, 'c': 0, 'd': 0}
- d2 = dict.fromkeys(['x', 'y', 'z']) # 值默认为 None
复制代码 注意:fromkeys 使用可变默认值(如空列表)时会导致所有键共享同一对象,应改用推导式。
- # 正确用法:每个键独立列表
- d3 = {k: [] for k in ['a', 'b']}
- d3['a'].append(1)
- print(d3) # {'a': [1], 'b': []}
复制代码
5. 从 Counter 创建- from collections import Counter
- words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
- counter = Counter(words)
- print(dict(counter)) # {'apple': 3, 'banana': 2, 'cherry': 1}
复制代码
6. 从 JSON 字符串创建- import json
- json_str = '{"name": "小明", "age": 25, "scores": [85, 92, 78]}'
- data = json.loads(json_str)
复制代码
三、底层原理:哈希表
字典的底层是哈希表:通过 hash() 函数将键转为整数,再映射到内部数组的索引。这是 O(1) 查找的根本原因。
- print(hash('hello')) # 整数(每次运行可能不同)
- print(hash(42)) # 42
- print(hash((1,2,3))) # 整数
复制代码
哈希冲突:不同键可能映射到同一索引,Python 使用开放寻址法解决。正常场景下不影响性能,极端情况(所有键哈希值相同)退化为 O(n)。
键的哈希性要求:键必须实现 __hash__ 和 __eq__。字符串、数字、布尔值、全不可变元组、None、frozenset 可哈希;列表、字典、集合不可哈希。自定义对象默认可哈希(基于 id),一旦定义 __eq__ 必须同时定义 __hash__。
Python 3.6+ 优化:采用紧凑存储(稠密表+索引表),内存减少 20-25%,并且 Python 3.7+ 官方保证保持插入顺序。
四、键类型深入与缺失键处理
90% 的字典键为字符串,便于序列化和配置。
常见错误:访问不存在的键引发 KeyError。四种处理方式:- d = {'name': '小明', 'age': 25}
- # 1. get() 返回默认值
- city = d.get('city', '未知') # '未知'
- # 2. in 检查
- if 'city' in d: city = d['city']
- # 3. setdefault() 获取并设置默认值
- city = d.setdefault('city', '北京') # 返回 '北京',并写入 d
- # 4. collections.defaultdict
- from collections import defaultdict
- words = ['apple', 'banana', 'apricot']
- by_first = defaultdict(list)
- for w in words:
- by_first[w[0]].append(w)
- print(dict(by_first)) # {'a': ['apple', 'apricot'], 'b': ['banana']}
复制代码
五、性能特征
常用操作时间复杂度:
- d[key]、d[key]=value、del d[key]、key in d 均为 O(1)
- 遍历、copy、clear 为 O(n)
- len(d) 是 O(1)(字典维护计数)
对比列表 in 查找:- import time
- n = 10000
- lst = list(range(n))
- d = {i: True for i in range(n)}
- start = time.perf_counter()
- for _ in range(10000):
- _ = 9999 in lst
- t_list = time.perf_counter() - start
- start = time.perf_counter()
- for _ in range(10000):
- _ = 9999 in d
- t_dict = time.perf_counter() - start
- print(f'列表查找: {t_list:.4f}s,字典查找: {t_dict:.4f}s,快 {t_list/t_dict:.0f} 倍')
复制代码 随 n 增大,字典优势更加明显。
六、字典视图:keys()、values()、items()
视图是动态的,反映字典的实时变化。- d = {'name': '小明', 'age': 25, 'city': '北京'}
- keys = d.keys()
- d['job'] = '工程师'
- print(keys) # 自动包含 'job'
复制代码 视图支持集合操作(keys 和 items):交集(&)、并集(|)、差集(-)、对称差(^)。values 通常不支持,因为值可能重复或不可哈希。
七、实战构建模式
查找表替代 if-elif 链- def get_status_text(code):
- status = {
- 200: '成功', 301: '永久重定向', 404: '未找到',
- 500: '服务器错误', 502: '网关错误', 503: '服务不可用',
- }
- return status.get(code, '未知状态')
复制代码
函数调度表- def add(a,b): return a+b
- def sub(a,b): return a-b
- ops = {'+': add, '-': sub, '*': lambda a,b: a*b, '/': lambda a,b: a/b if b else None}
- def calculate(op, a, b):
- func = ops.get(op)
- if func: return func(a, b)
- raise ValueError(f'不支持运算符: {op}')
复制代码
缓存装饰器- def memoize(func):
- cache = {}
- def wrapper(*args):
- if args not in cache:
- cache[args] = func(*args)
- return cache[args]
- return wrapper
- @memoize
- def fibonacci(n):
- if n < 2: return n
- return fibonacci(n-1) + fibonacci(n-2)
- print(fibonacci(40)) # 102334155,秒出
复制代码
八、小结
字典是 Python 开发中不可或缺的工具。掌握花括号、dict()、推导式、fromkeys、JSON 等创建方式,理解哈希表底层原理,正确处理缺失键,利用视图和集合操作,能够极大提升代码效率。下一篇将重点讲解字典的增删改查操作。 |