Skip to content

Python 字典 ​

列表适合保存一组同类数据,但只能通过位置找到元素。现在有一个新需求:保存学生的姓名、年龄和成绩,并且能直接根据字段名称找到对应内容,应该如何组织数据?

如果只看到 "小明"、18、95 这些值,很难判断每个值表示什么。字典可以同时保存字段名称和对应数据。

案例:保存学生信息 ​

需求:保存一名学生的姓名、年龄和成绩,并通过字段名称读取这些数据。

python
student = {
    "name": "小明",
    "age": 18,
    "score": 95,
}

print(student)

字典使用键值对保存数据。每个键对应一个值:

  • key 表示键,可以理解成字段名称
  • value 表示值,也就是字段对应的数据

字典键值对和根据键取值的示意图

字典创建 ​

字典使用 {} 创建。键和值之间用冒号分隔,多个键值对之间用逗号分隔:

python
student = {
    "name": "小明",
    "age": 18,
    "gender": "男",
    "height": 1.65,
}

创建字典时,先记住三个基本特点:

  1. 每个键必须唯一
  2. 值可以是任意数据类型
  3. 常见的键使用字符串

如果同一个字段被重复写入,后面的值会覆盖前面的值:

python
student = {
    "name": "小明",
    "name": "小红",
}

print(student)

运行结果:

text
{'name': '小红'}

需要从空数据开始收集信息时,可以使用 {} 或 dict() 创建空字典:

python
student1 = {}
student2 = dict()

print(student1)
print(student2)

键值访问 ​

字典不使用位置索引,而是通过键取得对应的值。读取数据前,先判断键是否一定存在。

方括号访问 ​

python
student = {
    "name": "小明",
    "age": 18,
    "score": 95,
}

print(student["name"])
print(student["score"])

运行结果:

text
小明
95

如果能够确定键一定存在,可以使用方括号;如果键不存在,方括号取值会抛出 KeyError:

text
KeyError: 'city'

get() 默认值访问 ​

如果键可能不存在,使用 get() 可以避免异常。找不到键时它默认返回 None,也可以设置一个默认值:

python
student = {
    "name": "小明",
    "age": 18,
}

print(student.get("name"))
print(student.get("city"))
print(student.get("city", "暂未填写"))

运行结果:

text
小明
None
暂未填写

可以按数据是否完整来选择:

  • 确定键一定存在,使用 dictionary[key]
  • 键可能不存在,希望提供默认值,使用 dictionary.get(key, default)

字典增删改 ​

学生信息录入后,年龄可能变化,也可能新增城市、删除无效字段。字典是可变容器,可以直接调整其中的键值对。

字典新增、修改和删除键值对的示意图

键值新增与修改 ​

新增和修改使用相同的语法,区别在于键是否已经存在:

python
student = {
    "name": "小明",
    "age": 18,
}

# 键已经存在,修改原来的值
student["age"] = 19

# 键不存在,新增一个键值对
student["city"] = "长沙"

print(student)

运行结果:

text
{'name': '小明', 'age': 19, 'city': '长沙'}

键值删除 ​

如果需要删除一个字段并继续使用它的值,pop() 会根据键删除数据,并返回被删除的值:

python
student = {
    "name": "小明",
    "age": 18,
    "city": "长沙",
}

city = student.pop("city")

print(city)
print(student)

运行结果:

text
长沙
{'name': '小明', 'age': 18}

只需要删除而不需要返回值时,可以使用 del student["age"];需要清空整个字典时,使用 clear()。

嵌套数据访问 ​

如果学生信息还包含地址和爱好,单层字典是否足够?字典的值可以继续保存列表、字典等容器,下面用嵌套结构保存这些信息:

python
student = {
    "name": "正心",
    "age": 18,
    "gender": "男",
    "height": 1.65,
    "info": {
        "address": "中国·湖南·长沙",
        "hobby": ["吃饭", "睡觉", "写代码"],
    },
}

print(student["name"])
print(student["info"]["address"])
print(student["info"]["hobby"][0])

取嵌套数据时,按“外层字典 → 内层字典 → 列表元素”的路径逐层分析:

  1. student["info"] 先取得内层字典
  2. student["info"]["hobby"] 再取得爱好列表
  3. student["info"]["hobby"][0] 最后取得列表中的第一个爱好

嵌套字典和列表逐层取值的示意图

修改嵌套数据时,沿着同样的路径定位目标:

python
student["info"]["address"] = "中国·广东·深圳"
student["info"]["hobby"].append("摄影")

print(student["info"])

案例:提取音乐接口数据 ​

问题:接口返回的数据层级较深,如何从中提取歌手、歌名和音乐地址?下面的数据和开发中常见的接口返回结果很像。

python
data = {
    "state": True,
    "errno": 22000,
    "errmsg": "",
    "elapsed_time": "0.0122",
    "data": {
        "artist": [
            {
                "artistCode": "A10047720",
                "birthday": "1983-07-17",
                "gender": "男",
                "name": "薛之谦",
                "artistType": 38,
                "artistTypeName": "歌手",
                "pic": "https://img01.dmhmusic.com/0101/M00/F8/AE/ChR45V7XEJWAZJ8KAAL8wj6aS4w115.jpg",
                "region": "",
            }
        ],
        "cpId": 23,
        "pic": "https://img01.dmhmusic.com/0412/M00/FE/96/ChAKEl_IzFiAUZyEAA-Q7DRXeIo850.jpg",
        "title": "你还要我怎样",
        "duration": 310,
        "assetId": "T10038986648",
        "genre": "流行",
        "albumTitle": "意外",
        "id": "T10038986648",
        "lang": "中文",
        "afReplayGain": -0.370003,
        "albumAssetCode": "P10001640807",
        "releaseDate": "2013-11-11T00:00:00.000Z",
        "isrc": "CNA651302299",
        "sort": 3,
        "meanVolume": -17.1,
        "maxVolume": 0,
        "lyric": "https://static-qianqian.taihe.com/0412/M00/FE/96/ChAKEl_IzFiAA1agAAAG_sbemkw066.lrc",
        "pay_model": 2,
        "TSID": "T10038986648",
        "allRate": ["320", "128", "3000", "64"],
        "pushTime": "2019-03-05T16:42:51+08:00",
        "downTime": "2037-01-01T00:00:00+08:00",
        "bizList": ["sdk_cpm"],
        "bits": 16,
        "path": "https://audio04.dmhmusic.com/71_53_T10038986648_128_4_1_0_sdk-cpm/cn/0208/M00/E5/61/ChR46119DrGAW4d4AEvErRDwLyg867.mp3?xcode=c1a597cce27ff185785e95c6cf346f934713ee4",
        "size": 4965549,
        "rate": 128,
        "hashcode": "cee20c55e4518f0511c101f76c2914c0ba9895b9",
        "format": "mp3",
        "filemd5": "feacadc129c14b6418d7bd12864b1757",
        "expireTime": 1609416230,
        "isFavorite": 0,
        "isVip": 0,
    },
}

先把数据结构拆成访问路径:

  1. 最外层的 "data" 对应一个字典
  2. 内层的 "artist" 对应一个列表
  3. 列表中的第一个元素又是字典
  4. 歌手姓名保存在这个字典的 "name" 中
参考代码
python
artist = data["data"]["artist"][0]["name"]
title = data["data"]["title"]
path = data["data"]["path"]

print("歌手:", artist)
print("歌名:", title)
print("音乐地址:", path)

预期输出:

text
歌手:薛之谦
歌名:你还要我怎样
音乐地址:https://audio04.dmhmusic.com/71_53_T10038986648_128_4_1_0_sdk-cpm/cn/0208/M00/E5/61/ChR46119DrGAW4d4AEvErRDwLyg867.mp3?xcode=c1a597cce27ff185785e95c6cf346f934713ee4

字典方法 ​

在 IPython 中输入 dictionary. 后按 Tab,可以查看字典支持的方法。先掌握下面这些常用操作:

text
clear       items       setdefault
copy        keys        update
fromkeys    pop         values
get         popitem
方法作用
get(key, default=None)取得指定键的值;键不存在时返回默认值
keys()取得所有键
values()取得所有值
items()取得所有键值对,遍历时常用
update(other_dict)使用另一个字典新增或更新键值对
pop(key)删除指定键并返回对应的值
clear()清空字典
copy()复制字典
python
student = {
    "name": "小明",
    "age": 18,
}

student.update({"age": 19, "city": "长沙"})

print(student.keys())
print(student.values())
print(student.items())
print(student)

update() 中的键已经存在时会更新,键不存在时会新增。

字典遍历 ​

键遍历 ​

如果只需要字段名称,直接遍历字典,默认取得每一个键:

python
student = {
    "name": "小明",
    "age": 18,
    "city": "长沙",
}

for key in student:
    print(key, student[key])

也可以写成 for key in student.keys(),运行效果相同。

键值对遍历 ​

如果需要同时处理字段名称和字段内容,items() 可以在每轮循环中取得键和值:

python
student = {
    "name": "小明",
    "age": 18,
    "city": "长沙",
}

for key, value in student.items():
    print(key, value)

只需要值时,可以遍历 student.values()。需要成对处理字段和值时,items() 最直接。

案例:统计字符 ​

问题:如何统计一句话中每个字符出现的次数?

python
text = "Beautiful is better than ugly."
char_count = {}

for char in text:
    if char not in char_count:
        char_count[char] = 1
    else:
        char_count[char] += 1

for char, count in char_count.items():
    print(f"字符 {char!r} 出现了 {count} 次")

功能分析:

  1. 创建空字典 char_count
  2. 依次取出字符串中的字符
  3. 字符第一次出现时,将次数设置为 1
  4. 字符再次出现时,将原次数增加 1
  5. 遍历字典并输出统计结果

使用字典统计字符出现次数的流程图

这里直接使用 char in char_count 判断键是否存在,不需要额外调用 keys()。

字符频次排序 ​

如果还需要按次数从多到少输出,就把字典的键值对转换成列表,再按每个元素的第二项排序。

下面的 get_count() 是排序时使用的辅助函数。函数会在后面的章节详细学习,这里先观察运行效果:

python
item_list = list(char_count.items())


def get_count(item):
    return item[1]


item_list.sort(key=get_count, reverse=True)

for char, count in item_list:
    print(f"字符 {char!r} 出现了 {count} 次")

小结 ​

本节先回答五个问题:

  1. 为什么使用字典?因为它能用键值对表达字段与数据的对应关系
  2. 如何安全取值?确定键存在时用方括号,键可能缺失时用 get()
  3. 如何维护字段?dictionary[key] = value 既能新增,也能修改,删除可使用 pop()、del 或 clear()
  4. 如何读取嵌套数据?沿着键和索引从外到内逐层访问
  5. 如何批量处理数据?根据需求遍历键、值或键值对,常用 items()

练习 ​

  1. 创建一个字典,保存自己的姓名、年龄和城市
  2. 取出姓名,使用 get() 读取一个不存在的键
  3. 新增 hobby,修改年龄,再删除城市
  4. 使用 for 循环打印所有键值对
  5. 统计字符串 "hello python" 中每个字符出现的次数