08 Python字典(dict)完全指南

154 阅读9分钟

1. 字典的基本概念

字典(dict)是Python中的一种可变映射类型,它使用键值对(key-value pair)的形式来存储数据。字典的特点包括:

  • 键(key)必须是不可变类型(如字符串、数字或元组)
  • 值(value)可以是任意Python对象
  • 键必须是唯一的
  • Python 3.7+版本中字典会保持插入顺序

2. 字典的创建方法

# 1. 使用花括号创建
# 最常用的创建方式,直观且灵活
# 可以在创建时直接初始化多个键值对
# 支持嵌套字典结构
student = {
    'name': '张三',  # 字符串类型的键值对
    'age': 20,      # 数字类型的值
    'scores': {'语文': 85, '数学': 92}  # 嵌套字典
}

# 2. 使用dict()构造函数
# 适用于键名符合变量命名规则的情况
# 代码更简洁,但不支持复杂的键名
info = dict(name='李四', age=22)  # 等同于 {'name': '李四', 'age': 22}

# 3. 使用字典推导式
# 适合批量创建有规律的键值对
# 代码简洁,可读性好
squares = {x: x**2 for x in range(5)}  # 创建数字及其平方的映射

# 4. 使用dict.fromkeys()创建具有默认值的字典
# 适合初始化具有相同默认值的多个键
# 注意:所有键会共享同一个默认值对象
keys = ['a', 'b', 'c']
default_dict = dict.fromkeys(keys, 0)  # 创建所有键对应值为0的字典

3. 字典的基本操作

3.1 访问和修改元素

# 创建示例字典
user = {
    'name': '张三',
    'age': 25,
    'email': 'zhangsan@example.com'
}

# 1. 访问元素的多种方式
# 使用方括号访问 - 最直接的方式,但键不存在时会抛出KeyError
print(user['name'])  # 输出:张三

# 使用get()方法访问 - 推荐的安全访问方式
# 第一个参数是键名,第二个参数是键不存在时的默认值
age = user.get('age', 0)  # 如果'age'键不存在,返回默认值0
phone = user.get('phone', '未设置')  # 获取可能不存在的键

# 2. 修改元素
# 直接赋值修改已存在的键值对
user['age'] = 26  # 修改已存在的键的值

# 3. 添加新元素
# 使用新键直接赋值
user['phone'] = '13800138000'  # 添加新的键值对

# 4. 删除元素的多种方式
# 使用del语句 - 删除指定键值对,键不存在会抛出KeyError
del user['email']

# 使用pop()方法 - 删除并返回值,可以提供默认值
phone = user.pop('phone', None)  # 如果键不存在,返回None

# 使用popitem()方法 - 删除并返回最后一个键值对
last_item = user.popitem()  # 返回元组(key, value)

3.2 常用字典方法

# 1. 获取字典信息的方法
# keys() - 获取所有键
# 返回一个动态视图对象,会随字典变化而更新
keys = user.keys()  # 获取所有键的视图
key_list = list(keys)  # 转换为列表

# values() - 获取所有值
# 同样返回动态视图对象
values = user.values()  # 获取所有值的视图
value_list = list(values)  # 转换为列表

# items() - 获取所有键值对
# 返回(key, value)元组的视图对象
items = user.items()  # 获取所有键值对的视图
for key, value in items:  # 常用于遍历字典
    print(f"{key}: {value}")

# 2. 修改字典的方法
# update() - 批量更新字典
# 可以使用另一个字典或键值对序列更新
user.update({'city': '北京', 'age': 27})  # 使用字典更新
user.update(zip(['hobby', 'job'], ['读书', '程序员']))  # 使用键值对序列更新

# setdefault() - 设置默认值
# 如果键不存在,则设置默认值并返回
# 如果键存在,则返回现有值
email = user.setdefault('email', 'default@example.com')

# 3. 清理字典的方法
# clear() - 清空字典
# 删除所有键值对
user.clear()  # 字典变为空字典 {}

# copy() - 创建字典的浅拷贝
# 创建新字典,但嵌套的可变对象仍然共享引用
user_copy = user.copy()

4. 字典的高级应用

4.1 嵌套字典

# 复杂的嵌套字典示例
# 展示了多层嵌套的数据结构
# 适用于表示层级关系的数据
school = {
    'class_1': {
        'teacher': '王老师',
        'students': {
            '001': {
                'name': '张三',
                'scores': {'语文': 85, '数学': 92}
            },
            '002': {
                'name': '李四',
                'scores': {'语文': 89, '数学': 94}
            }
        }
    }
}

# 访问嵌套数据的方法
# 1. 使用多级键访问
print(school['class_1']['students']['001']['scores']['语文'])  # 输出:85

# 2. 使用get()方法的安全访问
# 避免键不存在时的异常
score = school.get('class_1', {}).get('students', {}).get('001', {}).get('scores', {}).get('语文', 0)

# 3. 修改嵌套数据
# 逐层创建或修改数据
school['class_2'] = {}  # 创建新的班级
school['class_2']['teacher'] = '李老师'  # 添加教师信息
school['class_2']['students'] = {}  # 初始化学生字典

4.2 字典推导式高级用法

# 1. 条件字典推导式
# 根据条件筛选键值对
scores = {'张三': 85, '李四': 92, '王五': 78, '赵六': 95}
# 筛选成绩大于等于85分的学生
pass_students = {name: score for name, score in scores.items() if score >= 85}

# 2. 转换值的字典推导式
# 对原字典的值进行转换
grades = {name: 'A' if score >= 90 else 'B' if score >= 80 else 'C'
         for name, score in scores.items()}

# 3. 嵌套字典推导式
# 创建复杂的嵌套字典结构
# 生成乘法表字典
matrix = {i: {j: i*j for j in range(3)} for i in range(3)}
# 结果示例:{0: {0: 0, 1: 0, 2: 0}, 1: {0: 0, 1: 1, 2: 2}, 2: {0: 0, 1: 2, 2: 4}}

5. 使用字典格式化字符串

5.1 基本格式化

# 1. 使用%操作符(旧式格式化)
# 语法简单,但功能有限
info = {'name': '张三', 'age': 25}
print('%(name)s is %(age)d years old' % info)

# 2. 使用str.format()方法
# 更灵活,支持更多格式化选项
print('{name} is {age} years old'.format(**info))

# 3. 使用f-string(推荐,Python 3.6+)
# 最简洁、直观的方式
# 支持直接使用表达式
print(f"{info['name']} is {info['age']} years old")

5.2 高级格式化技巧

# 1. 对齐和填充
data = {'name': '张三', 'score': 95.5}
# 左对齐,宽度10
# 右对齐,宽度8,保留2位小数
print(f"{data['name']:<10}|{data['score']:>8.2f}")

# 2. 使用字典进行复杂模板格式化
# 适用于生成报告、配置文件等
template = '''
学生信息:
姓名:{name}
年龄:{age}
成绩:{score:.1f}
'''

student = {
    'name': '张三',
    'age': 18,
    'score': 95.6
}

print(template.format(**student))

6. 字典性能优化建议

6.1 字典创建优化

# 1. 预分配空间
# 当知道字典大小时,使用dict.fromkeys()预分配空间
# 避免频繁的内存重新分配
keys = range(10000)
optimized_dict = dict.fromkeys(keys)  # 预分配10000个空间

# 对比:动态增长的字典
unoptimized_dict = {}
for i in range(10000):
    unoptimized_dict[i] = None  # 会导致多次内存重新分配

# 2. 使用字典推导式替代循环
# 字典推导式通常比循环创建字典更快
squares_comprehension = {x: x**2 for x in range(1000)}  # 更快

squares_loop = {}
for x in range(1000):  # 更慢
    squares_loop[x] = x**2

6.2 访问优化

# 1. 使用get()方法的最佳实践
# 当确定键存在时,使用方括号访问更快
user = {'name': '张三', 'age': 25}

# 快:确定键存在时使用方括号
name = user['name']

# 慢:不必要的get()调用
name = user.get('name')

# 正确使用场景:键可能不存在时使用get()
phone = user.get('phone', '未设置')

# 2. 避免重复访问
# 对频繁访问的值进行本地缓存
def process_user_slow(user):
    # 差:重复访问字典
    print(f"姓名:{user['name']}")
    print(f"年龄:{user['age']}")
    if user['age'] > 18:
        print(f"{user['name']}是成年人")

def process_user_fast(user):
    # 好:将频繁使用的值缓存到局部变量
    name = user['name']
    age = user['age']
    print(f"姓名:{name}")
    print(f"年龄:{age}")
    if age > 18:
        print(f"{name}是成年人")

6.3 更新优化

# 1. 批量更新优化
# 使用update()方法进行批量更新,而不是多次单独更新
user = {'name': '张三'}

# 差:多次单独更新
user['age'] = 25
user['city'] = '北京'
user['email'] = 'zhangsan@example.com'

# 好:使用update()批量更新
user.update({
    'age': 25,
    'city': '北京',
    'email': 'zhangsan@example.com'
})

# 2. 避免频繁的增删操作
# 如果需要频繁增删,考虑使用collections.defaultdict或set
from collections import defaultdict

# 统计单词频率的例子
# 使用defaultdict避免键检查
word_counts = defaultdict(int)
text = "the quick brown fox jumps over the lazy dog"
for word in text.split():
    word_counts[word] += 1  # 无需检查键是否存在

6.4 内存优化

# 1. 使用__slots__优化类字典
# 当类属性固定时,使用__slots__可以显著减少内存使用
class UserNormal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

class UserOptimized:
    __slots__ = ['name', 'age']  # 显著减少内存使用
    def __init__(self, name, age):
        self.name = name
        self.age = age

# 2. 及时清理不需要的数据
# 使用clear()方法而不是重新赋值
large_dict = {i: i**2 for i in range(10000)}

# 差:直接赋值新字典
large_dict = {}  # 旧字典仍在内存中,等待垃圾回收

# 好:使用clear()清理
large_dict.clear()  # 立即释放内存

# 3. 使用弱引用字典
# 当需要缓存对象但不阻止它们被垃圾回收时
from weakref import WeakKeyDictionary

class Cache:
    def __init__(self):
        # 当键对象没有其他引用时,自动从字典中删除
        self.data = WeakKeyDictionary()

6.5 性能测试示例

import timeit
import sys

# 1. 字典创建性能对比
def test_dict_creation():
    # 测试不同创建方法的性能
    setup = """size = 1000"""
    
    test1 = """dict_comp = {x: x**2 for x in range(size)}"""
    test2 = """dict_loop = {}
for x in range(size):
    dict_loop[x] = x**2"""
    
    time1 = timeit.timeit(test1, setup, number=1000)
    time2 = timeit.timeit(test2, setup, number=1000)
    
    print(f"字典推导式: {time1:.4f}秒")
    print(f"循环创建: {time2:.4f}秒")

# 2. 内存使用对比
def compare_memory_usage():
    # 测试普通类和使用__slots__的类的内存占用
    normal_users = [UserNormal(f"user{i}", i) for i in range(1000)]
    optimized_users = [UserOptimized(f"user{i}", i) for i in range(1000)]
    
    normal_size = sys.getsizeof(normal_users[0]) * len(normal_users)
    optimized_size = sys.getsizeof(optimized_users[0]) * len(optimized_users)
    
    print(f"普通类对象占用内存: {normal_size/1024:.2f}KB")
    print(f"优化类对象占用内存: {optimized_size/1024:.2f}KB")

if __name__ == '__main__':
    print("性能测试结果:")
    test_dict_creation()
    print("\n内存使用对比:")
    compare_memory_usage()

6.6 最佳实践总结

  1. 创建优化

    • 预知大小时使用dict.fromkeys()预分配空间
    • 优先使用字典推导式而不是循环创建
    • 批量数据优先使用dict()构造函数
  2. 访问优化

    • 确定键存在时使用方括号访问
    • 键可能不存在时才使用get()
    • 频繁访问的值存储在局部变量中
  3. 更新优化

    • 多个键值对更新时使用update()
    • 频繁增删操作考虑使用defaultdict
    • 避免频繁的单键更新操作
  4. 内存优化

    • 属性固定的类使用__slots__
    • 及时使用clear()释放内存
    • 合理使用弱引用字典
    • 避免存储重复数据
  5. 其他建议

    • 使用适当的数据结构(如set代替值为None的字典)
    • 定期进行性能分析和内存监控
    • 在性能关键场景进行基准测试