字典这个数据结构活跃在所有 Python
程序的背后,即便你的源码里并没有直接用到它。
dict
类型不但在各种程序里广泛使用,它也是 Python
语言的基石
。模块的命名空间
、实例的属性
和函数的关键字参数
中都可以看到字典的身影。跟它有关的内置函数都在__builtins__
.__dict__
模块中。
正是因为字典至关重要,Python
对它的实现做了高度优化
,而散列表
则是字典类型性能出众的根本原因。
集合(set
)的实现其实也依赖于散列表
。反过来说,想要进一步理解集合和字典,就得先理解散列表的原理。
- 泛映射类型
collections.abc
模块中有Mapping 和 MutableMapping
这两个抽象基类
,它们的作用是为dict
和其他类似的类型
定义形式接口(在Python 2.6
到Python 3.2
的版本中,这些类还不属于collections.abc
模块,而是隶属于collections
模块)
然而,非抽象映射
类型一般不会
直接继承
这些抽象基类
,它们会直接对dict
或是collections.User.Dict
进行扩展。这些抽象基类的主要作用是作为形式化的文档
,它们定义了构建一个映射类型所需要的最基本的接口。然后它们还可以跟isinstance
一起被用来
判定
某个数据是不是广义上的映射类型。
from collections import abc
print(isinstance({}, abc.Mapping))
# True
这里用 isinstance
而不是 type
来检查某个参数是否为 dict
类型,因为这个参数有可能不是 dict
,而是一个比较另类的映射类型。
标准库里的所有映射类型都是利用 dict
来实现的,因此它们有个共同的限制,即只有可散列
的数据类型
才能用作这些映射里的键
(只有键有这个要求,值并不需要是可散列的数据类型)
- 什么是可散列的数据类型
在 Python 词汇表(https://docs.python.org/3/glossary.html#term-hashable
)中,关于可散列类型的定义有这样一段话:
如果一个对象是可散列的
,那么在这个对象的生命周期中
,它的散列值
是不变的
,而且这个对象需要实现__hash__()
方法。另外可散列对象还要有__qe__()
方法,这样才能跟其他键做比较。如果两个可散列对象是相等的
,那么它们的散列值一定是一样的
……
原子不可变数据类型(str
、bytes
和数值类型
)都是可散列类型,frozenset
也是可散列的,因为根据其定义,frozenset
里只能容纳可散列类型。元组
的话,只有当一个元组包含
的所有元素都是可散列类型
的情况下,它才是可散列的。来看下面的元组 tt、
tl 和 tf:
Python
词汇表(https://docs.python.org/3/glossary.html#term-hashable
)里还在说“Python
里所有的不可变
类
型都是可散列的”。这个说法其实是不准确的,比如虽然元组本身
是不可变序列,它里面的元素可能
是其他可变类型的引用
根据这些定义,字典提供了很多种构造方法“Built-in Types”(https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
)示例说明
a = dict(one=1, two=2, three=3)
b = {'one': 1, 'two': 2, 'three': 3}
c = dict(zip(['one', 'two', 'three'], [1, 2, 3]))
d = dict([('two', 2), ('one', 1), ('three', 3)])
e = dict({'three': 3, 'one': 1, 'two': 2})
print(a == b == c == d == e)
# True
除了这些字面句法和灵活的构造方法之外,字典推导
(dict comprehension
)也可以用来建造新 dict
- 常见的映射方法
映射类型的方法其实很丰富。下表为我们展示了dict
、defaultdict
和OrderedDict
的常见方法,后面两个数据类型是dict
的变种,位于collections
模块内。
dict
、collections.defaultdict
和collections.OrderedDict
这三种映射类型的方法列表(依然省略了继承自object的常见方法);可选参数以[…]表示
default_factory
并不是一个方法,而是一个可调用对象(callable
),它的值在defaultdict
初始化的时候由用户设定。
OrderedDict.popitem()
会移除字典里最先插入的元素(先进先出
);同时这个方法还有一个可选的last
参数,若为真,则会移除最后插入的元素(后进先出
)。
上面的表格中,update
方法处理参数 m
的方式,是典型的“鸭子类型
”。函数首先检查 m
是否有 keys
方法,如果有,那么 update
函数就把它当作映射对象来处理。否则,函数会退一步,转而把 m
当作包含了键值对 (key, value
) 元素的迭代器。Python
里大多数映射类型的构造方法都采用了类似的逻辑,因此你既可以
用一个映射对象来新建一个映射对象,也可以
用包含 (key, value)
元素的可迭代对象来初始化一个映射对象。