Python 字典(Dictionary)完全学习指南

字典(dict)是 Python 中唯一的映射型数据结构,核心特性是通过「键(key)-值(value)」对存储数据,查询效率极高(底层基于哈希表实现)。它是 Python 中最常用、最灵活的数据类型之一,适用于存储关联型数据(如配置信息、用户信息、映射关系等)。

本文将从基础定义→核心操作→进阶特性→应用场景→常见坑,系统讲解字典的全部知识,适合 Python 初学者循序渐进学习。

一、字典的基本概念

1. 核心特性

  • 映射关系:每个 key 唯一对应一个 value(key 不可重复,value 可重复)。
  • key 约束:key 必须是「不可变类型」(如整数、字符串、元组、布尔值),不能是列表、字典等可变类型(会触发 TypeError)。
  • value 无约束:value 可以是任意 Python 类型(整数、字符串、列表、字典、函数等)。
  • 无序性(Python 3.7 前):Python 3.7 之前字典是无序的,3.7+ 后保证插入顺序(但不依赖索引访问)。
  • 动态性:字典创建后可随时增删改键值对,无需指定固定长度。

2. 与列表(list)的区别

特性字典(dict)列表(list)
访问方式按 key 访问按索引访问
元素关系键值对映射有序元素集合
查找效率极高(O(1))较低(O(n))
元素唯一性key 唯一,value 可重复元素可重复
元素类型约束key 必须不可变,value 无约束元素无类型约束

二、字典的创建

Python 提供多种创建字典的方式,根据场景选择即可:

1. 字面量方式(最常用)

{} 包裹键值对,键值对之间用逗号 , 分隔,key 和 value 用冒号 : 连接。

# 基础字典(字符串key)
person = {"name": "张三", "age": 20, "gender": "男"}

# 整数key(适合映射关系)
score = {1: 90, 2: 85, 3: 95}

# 混合类型key(不推荐,但语法允许)
mixed_dict = {True: "布尔值", (1, 2): "元组key", 3.14: "浮点数"}

# 空字典(后续动态添加元素)
empty_dict = {}

2. dict() 构造函数

适合从其他数据结构(如列表、元组)转换为字典,或动态传入键值对。

# 方式1:传入键值对参数
person = dict(name="张三", age=20, gender="男")  # 键无需引号(本质是关键字参数)

# 方式2:传入二维可迭代对象(每个子元素是长度为2的序列)
list_data = [["name", "李四"], ("age", 22), ["gender", "女"]]
person2 = dict(list_data)  # 结果:{'name': '李四', 'age': 22, 'gender': '女'}

# 方式3:传入两个可迭代对象(用zip()配对键和值)
keys = ["name", "age", "gender"]
values = ["王五", 25, "男"]
person3 = dict(zip(keys, values))  # zip(keys, values) 生成键值对迭代器

3. 字典推导式(高效创建)

类似列表推导式,通过表达式快速生成字典(适合数据转换/过滤)。

# 示例1:生成「数字→平方」的字典
square_dict = {x: x**2 for x in range(1, 6)}  # 结果:{1:1, 2:4, 3:9, 4:16, 5:25}

# 示例2:过滤偶数的平方
even_square = {x: x**2 for x in range(1, 6) if x % 2 == 0}  # 结果:{2:4, 4:16}

# 示例3:转换现有字典(修改key/value)
person = {"name": "张三", "age": 20}
upper_person = {k.upper(): v for k, v in person.items()}  # 结果:{'NAME': '张三', 'AGE': 20}

4. fromkeys() 方法(创建默认值字典)

快速创建一个「所有 key 对应相同默认值」的字典(注意:默认值是共享引用,可变类型需谨慎)。

# 方式1:key列表 + 默认值(不可变类型,安全)
keys = ["a", "b", "c"]
dict1 = dict.fromkeys(keys, 0)  # 结果:{'a':0, 'b':0, 'c':0}

# 方式2:key列表 + 可变类型(危险!所有key共享同一个列表)
dict2 = dict.fromkeys(keys, [])  # 所有value指向同一个空列表
dict2["a"].append(1)  # 修改一个key的value,所有key的value都会变
print(dict2)  # {'a': [1], 'b': [1], 'c': [1]}(坑点!)

# 解决可变类型共享问题:用字典推导式
dict3 = {k: [] for k in keys}  # 每个key对应独立的空列表
dict3["a"].append(1)
print(dict3)  # {'a': [1], 'b': [], 'c': []}(正确)

三、字典的核心操作(增删改查)

字典的核心操作围绕「键(key)」展开,所有操作都通过 key 定位元素。

1. 查:访问字典的值

有 3 种常用方式,推荐使用 get() 方法(避免 KeyError)。

(1)按 key 直接访问(dict[key]
  • 若 key 存在,返回对应 value;
  • 若 key 不存在,直接触发 KeyError(程序崩溃),不推荐直接使用。
person = {"name": "张三", "age": 20}
print(person["name"])  # 输出:张三
print(person["height"])  # 报错:KeyError: 'height'
(2)get() 方法(推荐)

语法:dict.get(key, default=None)

  • 若 key 存在,返回对应 value;
  • 若 key 不存在,返回默认值(默认是 None,可自定义),不报错
person = {"name": "张三", "age": 20}
print(person.get("name"))  # 输出:张三
print(person.get("height"))  # 输出:None(无报错)
print(person.get("height", 175))  # 输出:175(自定义默认值)
(3)setdefault() 方法(查询+默认赋值)

语法:dict.setdefault(key, default=None)

  • 若 key 存在,返回对应 value;
  • 若 key 不存在,给 key 赋值默认值并返回该默认值(会修改原字典)。
person = {"name": "张三", "age": 20}
print(person.setdefault("name", "李四"))  # 输出:张三(key存在,不修改)
print(person.setdefault("height", 175))  # 输出:175(key不存在,添加到字典)
print(person)  # {'name': '张三', 'age': 20, 'height': 175}

2. 增:添加键值对

有 2 种常用方式,本质都是「给不存在的 key 赋值」。

(1)直接赋值(dict[key] = value

若 key 不存在,则添加新键值对;若 key 存在,则修改原有 value(见「改」操作)。

person = {"name": "张三", "age": 20}
person["height"] = 175  # 添加新key:height
person["gender"] = "男"  # 添加新key:gender
print(person)  # {'name': '张三', 'age': 20, 'height': 175, 'gender': '男'}
(2)update() 方法(批量添加/修改)

语法:dict.update(other_dict/iterable/key-value)

  • 接收字典、二维可迭代对象或键值对参数,批量添加/修改键值对;
  • 若 key 已存在,覆盖 value;若不存在,新增键值对。
person = {"name": "张三", "age": 20}

# 方式1:传入字典
person.update({"height": 175, "gender": "男"})
print(person)  # {'name': '张三', 'age': 20, 'height': 175, 'gender': '男'}

# 方式2:传入键值对参数
person.update(height=180, city="北京")  # 覆盖height,新增city
print(person)  # {'name': '张三', 'age': 20, 'height': 180, 'gender': '男', 'city': '北京'}

# 方式3:传入二维可迭代对象
person.update([("job", "工程师"), ("salary", 15000)])
print(person)  # 新增job和salary

3. 改:修改已有键的值

修改逻辑和「添加」一致:给已存在的 key 重新赋值。

person = {"name": "张三", "age": 20}
person["age"] = 21  # key已存在,修改value
person.update({"height": 175, "age": 22})  # update也可修改已有key
print(person)  # {'name': '张三', 'age': 22, 'height': 175}

4. 删:删除键值对

有 4 种常用方式,根据是否需要返回删除的 value 选择。

(1)del 语句(直接删除,无返回值)

语法:del dict[key]

  • 若 key 存在,删除该键值对;
  • 若 key 不存在,触发 KeyError
person = {"name": "张三", "age": 20, "height": 175}
del person["height"]  # 删除height键值对
print(person)  # {'name': '张三', 'age': 20}
del person["gender"]  # 报错:KeyError: 'gender'
(2)pop() 方法(删除并返回 value)

语法:dict.pop(key, default)

  • 若 key 存在,删除该键值对并返回 value;
  • 若 key 不存在,返回默认值(无默认值则触发 KeyError)。
person = {"name": "张三", "age": 20, "height": 175}
age = person.pop("age")  # 删除age,返回20
print(age)  # 20
print(person)  # {'name': '张三', 'height': 175}

# 不存在的key,指定默认值
gender = person.pop("gender", "未知")
print(gender)  # 未知(无报错)
(3)popitem() 方法(删除最后插入的键值对)

语法:dict.popitem()

  • Python 3.7+ 中,删除「最后插入」的键值对;3.7 前删除随机键值对;
  • 返回被删除的键值对(元组形式 (key, value));
  • 空字典调用会触发 KeyError
person = {"name": "张三", "age": 20, "height": 175}
last_item = person.popitem()  # 删除最后插入的height
print(last_item)  # ('height', 175)
print(person)  # {'name': '张三', 'age': 20}
(4)clear() 方法(清空字典)

语法:dict.clear()

  • 删除字典中所有键值对,字典变为空字典(保留字典对象本身)。
person = {"name": "张三", "age": 20}
person.clear()
print(person)  # {}

四、字典的遍历(迭代)

字典的遍历可针对「key」「value」「键值对」三种维度,使用 for 循环即可。

1. 遍历所有 key(默认遍历方式)

person = {"name": "张三", "age": 20, "gender": "男"}
for key in person:  # 等价于 for key in person.keys()
    print(key)  # 输出:name, age, gender(顺序与插入一致)

2. 遍历所有 value

values() 方法获取所有 value 的迭代器:

for value in person.values():
    print(value)  # 输出:张三, 20, 男

3. 遍历所有键值对(最常用)

items() 方法获取所有键值对的迭代器(每个元素是 (key, value) 元组):

for key, value in person.items():  # 解包元组(推荐)
    print(f"{key}: {value}")
# 输出:
# name: 张三
# age: 20
# gender: 男

# 不解包的写法(不推荐)
for item in person.items():
    print(item)  # 输出:('name', '张三'), ('age', 20), ('gender', '男')

遍历注意事项

  • 遍历过程中不能直接删除/添加键值对(会改变字典大小,触发 RuntimeError);
  • 若需遍历中修改,可先复制字典的 key 或 items(如 for key in list(person.keys())):
    person = {"name": "张三", "age": 20, "gender": "男"}
    # 遍历中删除key(需先转成列表)
    for key in list(person.keys()):
        if key == "age":
            del person[key]
    print(person)  # {'name': '张三', 'gender': '男'}(正确)
    

五、字典的常用方法(进阶)

除了增删改查,字典还有以下高频实用方法:

方法名功能描述
keys()返回所有 key 的视图对象(类似列表,可迭代,3.7+ 有序)
values()返回所有 value 的视图对象(可迭代)
items()返回所有键值对的视图对象(可迭代)
copy()浅拷贝字典(创建新字典,key 和不可变 value 共享,可变 value 仍引用同一对象)
fromkeys(seq, val)用序列 seq 中的元素作为 key,val 作为默认 value 创建新字典(见创建部分)
clear()清空字典
get(key, def)按 key 查询 value,不存在返回默认值
setdefault(key, def)查询 key,不存在则赋值默认值并返回
pop(key, def)删除 key 并返回 value,不存在返回默认值
popitem()删除最后插入的键值对并返回
update(...)批量添加/修改键值对

重点方法详解

1. 视图对象(keys()/values()/items()
  • 视图对象(view objects)是动态的:原字典修改后,视图对象会同步更新;
  • 视图对象不可直接修改,但可迭代、转换为列表(list())。
person = {"name": "张三", "age": 20}
keys = person.keys()
print(keys)  # dict_keys(['name', 'age'])(视图对象)

# 原字典修改,视图对象同步更新
person["gender"] = "男"
print(keys)  # dict_keys(['name', 'age', 'gender'])

# 转换为列表(静态,不再同步)
key_list = list(keys)
person["height"] = 175
print(key_list)  # ['name', 'age', 'gender'](不包含height)
2. 字典拷贝(copy() vs 深拷贝)

字典的 copy()浅拷贝(shallow copy):

  • 若 value 是不可变类型(如整数、字符串),拷贝后完全独立;
  • 若 value 是可变类型(如列表、字典),拷贝后仍共享该可变对象(修改一个会影响另一个)。
# 浅拷贝示例(可变value共享问题)
person = {"name": "张三", "hobbies": ["篮球", "游戏"]}
person_copy = person.copy()  # 浅拷贝

person_copy["name"] = "李四"  # 不可变value,修改不影响原字典
person_copy["hobbies"].append("读书")  # 可变value,修改影响原字典

print(person)  # {'name': '张三', 'hobbies': ['篮球', '游戏', '读书']}(hobbies被修改)
print(person_copy)  # {'name': '李四', 'hobbies': ['篮球', '游戏', '读书']}

解决浅拷贝问题:深拷贝(copy.deepcopy()
需导入 copy 模块,深拷贝会递归复制所有层级的对象,完全独立。

import copy

person = {"name": "张三", "hobbies": ["篮球", "游戏"]}
person_deepcopy = copy.deepcopy(person)  # 深拷贝

person_deepcopy["hobbies"].append("读书")  # 修改可变value,不影响原字典
print(person)  # {'name': '张三', 'hobbies': ['篮球', '游戏']}(原字典不变)
print(person_deepcopy)  # {'name': '张三', 'hobbies': ['篮球', '游戏', '读书']}

六、字典的进阶特性

1. 嵌套字典(字典中包含字典)

实际开发中常用嵌套字典存储复杂数据(如多级配置、JSON 数据)。

# 嵌套字典示例:用户信息(包含地址子字典)
user = {
    "id": 1001,
    "name": "张三",
    "info": {
        "age": 20,
        "gender": "男",
        "address": {
            "province": "广东",
            "city": "深圳"
        }
    }
}

# 访问嵌套字典的值(链式访问)
print(user["info"]["age"])  # 20
print(user["info"]["address"]["city"])  # 深圳

# 修改嵌套字典的值
user["info"]["address"]["city"] = "广州"
print(user["info"]["address"]["city"])  # 广州

# 避免嵌套KeyError:多层get()
city = user.get("info", {}).get("address", {}).get("city", "未知")
print(city)  # 广州(若中间key不存在,返回"未知")

2. 字典与 JSON 转换

字典是 Python 中与 JSON 格式最对应的结构(JSON 的 object 对应 Python 的 dict)。需使用 json 模块:

import json

# 字典转JSON字符串
person = {"name": "张三", "age": 20, "gender": "男"}
json_str = json.dumps(person, ensure_ascii=False)  # ensure_ascii=False:保留中文
print(json_str)  # {"name": "张三", "age": 20, "gender": "男"}

# JSON字符串转字典
json_str = '{"name": "李四", "age": 22, "gender": "女"}'
person_dict = json.loads(json_str)
print(person_dict)  # {'name': '李四', 'age': 22, 'gender': '女'}

3. 字典的比较

字典比较只看键值对是否完全一致(与插入顺序无关,3.7+ 顺序不同但键值对相同也判定为相等)。

dict1 = {"a": 1, "b": 2}
dict2 = {"b": 2, "a": 1}
print(dict1 == dict2)  # True(键值对相同,顺序无关)
print(dict1 != dict2)  # False

dict3 = {"a": 1, "b": 3}
print(dict1 == dict3)  # False(value不同)

七、字典的应用场景

字典的核心优势是「快速查找」和「关联映射」,常见应用场景:

  1. 存储配置信息:如数据库连接配置、程序参数。
    config = {
        "host": "localhost",
        "port": 3306,
        "user": "root",
        "password": "123456",
        "database": "test"
    }
    
  2. 数据统计:如统计字符串中字符出现次数(用键存字符,值存次数)。
    s = "hello python"
    count = {}
    for char in s:
        count[char] = count.get(char, 0) + 1  # 不存在则默认0,+1后存入
    print(count)  # {'h':1, 'e':1, 'l':2, 'o':2, ' ':1, 'p':1, 'y':1, 't':1, 'n':1}
    
  3. 替代多条件判断:用字典映射替代 if-elif-else,代码更简洁。
    # 传统if-elif
    def func(op):
        if op == "add":
            return 1 + 2
        elif op == "sub":
            return 3 - 1
        elif op == "mul":
            return 2 * 3
    
    # 字典映射(更简洁)
    def func(op):
        op_dict = {
            "add": 1 + 2,
            "sub": 3 - 1,
            "mul": 2 * 3
        }
        return op_dict.get(op, "无效操作")
    
  4. 缓存数据:利用字典的 O(1) 查找效率,缓存计算结果(避免重复计算)。
    cache = {}
    def calculate(x):
        if x in cache:  # 已缓存,直接返回
            return cache[x]
        result = x ** 2  # 未缓存,计算后存入
        cache[x] = result
        return result
    

八、常见错误与避坑指南

1. KeyError(键不存在)

  • 原因:用 dict[key] 访问不存在的 key;
  • 解决:用 get() 方法并指定默认值,或先判断 key 是否存在(if key in dict:)。
    person = {"name": "张三"}
    # 错误:print(person["age"])
    # 正确:
    print(person.get("age", 20))  # 20
    if "age" in person:
        print(person["age"])
    

2. key 必须是不可变类型

  • 错误示例:用列表、字典作为 key(触发 TypeError);
  • 正确示例:用字符串、整数、元组(元素也需不可变)作为 key。
    # 错误:dict1 = {[1,2]: "列表key"}  # TypeError: unhashable type: 'list'
    # 正确:dict1 = {(1,2): "元组key"}
    

3. fromkeys() 可变默认值共享

  • 原因:fromkeys() 的默认值是共享引用,可变类型(列表、字典)会被所有 key 共用;
  • 解决:用字典推导式替代 fromkeys()(见创建部分示例)。

4. 遍历中修改字典大小

  • 原因:遍历字典时直接删除/添加键值对,改变字典长度;
  • 解决:遍历字典的副本(如 list(dict.keys()))或先收集要修改的 key。
    person = {"name": "张三", "age": 20, "gender": "男"}
    # 错误:for key in person: del person[key](RuntimeError)
    # 正确:遍历key的列表副本
    for key in list(person.keys()):
        del person[key]
    print(person)  # {}
    

5. 浅拷贝导致的可变值共享

  • 原因:copy() 是浅拷贝,嵌套的可变类型 value 仍共享;
  • 解决:需要完全独立的字典时,用 copy.deepcopy()

九、总结

字典是 Python 中最核心的数据结构之一,核心要点:

  1. 本质是「键值对映射」,key 唯一且不可变,value 无约束;
  2. 核心优势是 O(1) 查找效率,远超列表;
  3. 常用操作:增(dict[key]=val/update())、删(del/pop()/clear())、改(重新赋值)、查(get() 推荐);
  4. 进阶特性:嵌套字典、字典推导式、浅/深拷贝、JSON 转换;
  5. 避坑重点:避免 KeyError、不使用可变类型作为 key、注意 fromkeys() 和浅拷贝的共享问题。

建议多通过「数据统计」「配置存储」等场景练习,熟练掌握字典的使用,它会成为你 Python 编程中最常用的工具之一!

小练习

  1. 用字典推导式生成 1-10 中奇数对应的立方字典(如 {1:1, 3:27, ...});
  2. 统计一个列表中各元素出现的次数(如 [1,2,2,3,3,3]{1:1, 2:2, 3:3});
  3. 实现一个嵌套字典,存储 3 个学生的姓名、年龄、科目成绩(语文/数学/英语),并计算每个学生的平均分。

更多推荐