字典 dict 把可哈希的键映射到值。Python 3.11 中字典保持插入次序;迭代和 in 默认面向键,值可以重复。判断键能否使用,应看哈希与相等规则,不能简单等同于“这个对象不可变”。
下列程序分别可独立运行,示例中的 assert 用于验证结果,运行时不要使用 -O。
创建、访问、更新与键的唯一性
跳转到“创建、访问、更新与键的唯一性”assert {} == dict()student = {"Name": "Runoob", "Age": 7, "Class": "First"}assert student["Age"] == 7student["Age"] = 8assert list(student) == ["Name", "Age", "Class"]assert "Age" in student and 8 not in student and 8 in student.values()assert dict([("a", 1), ("a", 2)]) == {"a": 2}assert student.update({"Score": 90}, Class="Second") is Noneassert student["Score"] == 90 and student["Class"] == "Second"assert list(student) == ["Name", "Age", "Class", "Score"]del student["Name"]student["Name"] = "Again"assert list(student)[-1] == "Name"
same_key = {1: "integer", True: "boolean", 1.0: "float"}assert len(same_key) == 1 and same_key[1] == "float"assert hash(1) == hash(True) == hash(1.0)mapping = {("sensor", 1): "ready", frozenset({1, 2}): "group"}assert mapping[("sensor", 1)] == "ready"for key in ([1], ([1], 2)): try: mapping[key] = "invalid" except TypeError: pass else: raise AssertionError("unhashable dictionary key was accepted")print("dictionary keys and insertion-order checks passed")重复给同一个键赋值会覆盖其值,字典不会同时保留两份相同键的条目。更新既有键不把它移到末尾;删除再插入则形成新的插入位置。整数 1、布尔值 True 和浮点数 1.0 相等且哈希相同,因此这里代表同一个键。
包含列表的元组仍不可哈希;自定义对象则需要其哈希和相等行为保持符合契约。更多边界见元组及比较与哈希。
查询和删除方法的区别
跳转到“查询和删除方法的区别”| 方法 | 行为 | 缺失键或空字典 |
|---|---|---|
get(key, default=None) | 返回对应值,否则返回 default | 不插入新条目 |
setdefault(key, default=None) | 已存在则返回原值,否则插入并返回 default | 会改变字典 |
pop(key, default) | 删除并返回对应值 | 无 default 时缺失键抛出 KeyError;default 只能有一个 |
popitem() | 删除并返回最后插入的一对键和值 | 空字典抛出 KeyError,不是随机删除 |
clear() | 清空现有字典 | 返回 None,其他别名会看到变化 |
update(other, **kwargs) | 从映射或键值对序列更新,再应用关键字项 | 返回 None,冲突时后面的值覆盖前面的值 |
copy() | 浅拷贝外层字典 | 嵌套对象仍然共享 |
fromkeys(iterable, value=None) | 用给定键建立新字典 | 所有键最初引用同一个 value |
d = {"a": 1}assert d.get("missing") is None and "missing" not in dassert d.get("missing", 99) == 99assert d.setdefault("b", 2) == 2 and d == {"a": 1, "b": 2}assert d.setdefault("b", 99) == 2assert d.pop("a") == 1assert d.pop("missing", 99) == 99try: d.pop("missing")except KeyError: passelse: raise AssertionError("missing pop without default succeeded")
d["c"] = 3d["b"] = 20assert d.popitem() == ("c", 3)assert d.popitem() == ("b", 20)try: d.popitem()except KeyError: passelse: raise AssertionError("popitem succeeded on an empty dictionary")
d.update([("x", 1), ("y", 2)], x=3)assert d == {"x": 3, "y": 2}alias = dassert d.clear() is None and alias == {}del dassert alias == {}print("dictionary lookup and removal checks passed")popitem() 的后进先出顺序从 Python 3.7 起得到保证。del d 删除变量绑定;它与 d.clear() 修改对象是不同操作。还要注意:方法参数在调用前就会求值,不能把 setdefault(key, expensive()) 当成只在缺失键时才调用 expensive 的惰性接口。
默认值共享与浅拷贝
跳转到“默认值共享与浅拷贝”原文的 fromkeys 很容易在可变默认值上造成误解。下面既验证共享结果,也给出每个键各自创建列表的方式。
shared = dict.fromkeys(["a", "b"], [])shared["a"].append(1)assert shared == {"a": [1], "b": [1]}assert shared["a"] is shared["b"]
separate = {key: [] for key in ["a", "b"]}separate["a"].append(1)assert separate == {"a": [1], "b": []}assert separate["a"] is not separate["b"]shallow = separate.copy()assert shallow is not separate and shallow["a"] is separate["a"]shallow["a"].append(2)shallow["new"] = []assert separate["a"] == [1, 2] and "new" not in separate
calls = []def make_default(): calls.append("called") return []
d = {"existing": [3]}assert d.setdefault("existing", make_default()) == [3]assert calls == ["called"]sentinel = object()assert {"value": None}.get("value", sentinel) is Noneassert {}.get("value", sentinel) is sentinelprint("dictionary defaults and shallow-copy checks passed")需要区分“没有这个键”和“这个键对应 None”时,使用不会与业务值混淆的标记对象,或者先明确检查成员关系。深拷贝的范围另见浅拷贝与深拷贝。
视图会反映字典变化
跳转到“视图会反映字典变化”keys()、values()、items() 返回动态视图,不是列表。若需要当时的外层条目快照,可以显式用 list(...) 保存;其中的可变值仍是引用。字典和这些视图都按插入次序遍历,reversed() 按相反次序遍历。
d = {"a": 1, "b": 2}keys, values, items = d.keys(), d.values(), d.items()snapshot = list(items)d["c"] = 3assert list(keys) == ["a", "b", "c"]assert list(values) == [1, 2, 3]assert list(items) == [("a", 1), ("b", 2), ("c", 3)]assert snapshot == [("a", 1), ("b", 2)]assert list(reversed(d)) == ["c", "b", "a"]assert list(reversed(items)) == [("c", 3), ("b", 2), ("a", 1)]assert keys & {"b", "z"} == {"b"}assert ("a", 1) in items
iterator = iter(d)assert next(iterator) == "a"d["new"] = 4try: next(iterator)except RuntimeError: passelse: raise AssertionError("size-changing iteration bug was not detected")for key in list(d): if key == "new": del d[key]assert list(d) == ["a", "b", "c"]print("dictionary view and iteration checks passed")遍历期间添加或删除条目可能引发异常或漏过条目,不应依赖某次运行碰巧可用。需要删除选中的键时,可以像例子一样遍历已取得的键列表,或先收集需要删除的键。视图规则见 Python 字典视图。
合并、格式化与特殊名称
跳转到“合并、格式化与特殊名称”Python 3.9 起,普通字典可以用 left | right 创建合并结果,冲突键采用右侧值;|= 原地更新,且像 update 一样可以接收键值对序列。这里的合并仍是浅层操作。
import pickleimport sys
left, right = {"a": 1, "same": "left"}, {"b": 2, "same": "right"}merged = left | rightassert merged == {"a": 1, "same": "right", "b": 2}assert left["same"] == "left"left |= [("extra", 3)]assert left["extra"] == 3try: right | [("extra", 3)]except TypeError: passelse: raise AssertionError("ordinary dict union accepted a pair list")
d = {"a": 1}assert "{a}".format(**d) == "1"assert format(d, "") == str(d)try: format(d, ".2f")except TypeError: passelse: raise AssertionError("dictionary accepted numeric format")assert d.__class__ is dict and isinstance(dict.__doc__, str)assert "items" in dir(d) and len(d) == 1assert sys.getsizeof(d) >= d.__sizeof__()assert pickle.loads(pickle.dumps(d, protocol=2)) == dprint("dictionary union, formatting and metadata checks passed")"{a}".format(**d) 调用的是字符串的格式化方法,把字典展开为关键字参数;不能据此认为字典的 __format__ 支持任意数值格式。原表涉及的特殊名称可按下表查找。
| 常用操作 | 对应名称 | 说明 |
|---|---|---|
| 类型、文档、属性目录 | __class__、__doc__、__dir__ | 前两个是属性,日常用 type 和 dir |
| 长度、对象内存大小 | __len__、__sizeof__ | len 数条目;sys.getsizeof 不递归统计所有键和值 |
| 表示与格式化 | __repr__、__str__、__format__ | 不等同于对另一个模板字符串执行 format |
| 下标读写、删除 | __getitem__、__setitem__、__delitem__ | 以键定位;不是位置索引 |
| 遍历、反向遍历、成员判断 | __iter__、__reversed__、__contains__ | 默认面向键 |
| 相等、不等 | __eq__、__ne__ | 内容相等不要求插入次序相同;普通 dict 没有大小排序 |
| pickle 重建 | __reduce_ex__ | 常规代码通过 pickle 使用,不能恢复不可信字节 |
完整方法说明见 Python 字典类型,内存统计见 sys.getsizeof,序列化边界见特殊方法与对象协议。