本文最后更新于:2024年5月7日 下午
Python 字典提供了散列查询的功能,使用灵活效率高,本文记录相关内容。
定义方式
字典是一种可变容器模型,且可存储任意类型对象
- 核心目的是要为每对记录提供 key 和 value,key 一定要可哈希的对象
1 |
|
字典推导
1 |
|
常见的映射方法
-
映射类型的方法其实很丰富,常用的有 dict、defaultdict 和 OrderedDict 的常见方法,后面两个数据类型 是 dict 的变种,位于 collections 模块内。
-
dict、collections.defaultdict和 collections.OrderedDict这三种映射类型的方法列表:
方法 dict defaultdict ordereddict 方法描述 d.clear()
√ √ √ 移除所有元素 d.__contains__(k)
√ √ √ 检查 k 是否在 d 中 d.copy()
√ √ √ 浅复制 d.__copy__()
√ 用于支持 copy.copy d.default_factory
√ 在 __missing__
函数中被调用的 函数,用以给未找到的元素设置 值,default_factory 并不是一个方法,而是一个可调用对象(callable),它的值在 defaultdict 初始化的时候由用户设定。d.__delitem__(k)
√ √ √ del d[k],移除键为 k 的元素 d.fromkeys(it, [initial])
√ √ √ 将迭代器 it 里的元素设置为映射 里的键,如果有 initial 参数, 就把它作为这些键对应的值(默 认是 None) d.get(k, [default])
√ √ √ 返回键 k 对应的值,如果字典里没有键 k,则返回 None 或者 default d.__getitem__(k)
√ √ √ 让字典 d 能用 d[k] 的形式返回键 k 对应的值 d.items()
√ √ √ 返回 d 里所有的键值对 d.__iter__()
√ √ √ 获取键的迭代器 d.keys()
√ √ √ 获取所有的键 d.__len__()
√ √ √ 可以用 len(d) 的形式得到字典里键值对的数量 d.__missing__(k)
√ 当 __getitem__
找不到对应键的 时候,这个方法会被调用d.move_to_end(k, [last])
√ 把键为 k 的元素移动到最靠前或者最靠后的位置(last 的默认值 是 True) d.pop(k, [defaul]
√ √ √ 返回键 k 所对应的值,然后移除 这个键值对。如果没有这个键, 返回 None 或者 default d.popitem()
√ √ √ 随机返回一个键值对并从字典里移除,OrderedDict.popitem() 会移除字典里最先插入的元素(先进先出);同时这个方法还有一 个可选的 last 参数,若为真,则会移除最后插入的元素(后进先出)。 d.__reversed__()
√ 返回倒序的键的迭代器 d.setdefault(k, [default])
√ √ √ 若字典里有键k,则把它对应的值 设置为 default,然后返回这个 值;若无,则让 d[k] =default
,然后返回 defaultd.__setitem__(k, v)
√ √ √ 实现 d[k] = v
操作,把 k 对应的 值设为vd.update(m, [**kargs])
√ √ √ m 可以是映射或者键值对迭代 器,用来更新 d 里对应的条目 d.values()
√ √ √ 返回字典里的所有值 -
setdefault 方法可以作为创建字典键值对的简化方法
1
my_dict.setdefault(key, []).append(new_value)
等价于
1
2
3if key not in my_dict:
my_dict[key] = []
my_dict[key].append(new_value)
-
映射的弹性键查询
有时候为了方便起见,就算某个键在映射里不存在,我们也希望在通过 这个键读取值的时候能得到一个默认值。有两个途径能帮我们达到这个目的,一个是通过 defaultdict,这个类型而不是普通的 dict,另一个 是给自己定义一个 dict 的子类,然后在子类中实现 __missing__
方法。
defaultdict
- 建立对象时接受可调用的对象作为参数送入 default_factory,当查找值不在字典中时调用对象创建对象填入字典
1 |
|
- 当需要为不存在的键值创建空列表对象时,可以:
1 |
|
-
如果在创建 defaultdict 的时候没有指定 default_factory,查询不 存在的键会触发 KeyError。
-
defaultdict 里的 default_factory 只会在
__getitem__
里被调用,在其他的方法里完全不会发挥作用。比 如,dd 是个 defaultdict,k 是个找不到的键, dd[k] 这个表达 式会调用 default_factory 创造某个默认值,而 dd.get(k) 则会 返回 None。所有这一切背后的功臣其实是特殊方法
__missing__
。它会在 defaultdict 遇到找不到的键的时候调用 default_factory,而实际
上这个特性是所有映射类型都可以选择去支持的。
__missing__
所有的映射类型在处理找不到的键的时候,都会牵扯到 __missing__
方法。这也是这个方法称作“missing”的原因。虽然基类 dict 并没有定 义这个方法,但是 dict 是知道有这么个东西存在的。也就是说,如果 有一个类继承了 dict,然后这个继承类提供了 __missing__
方法,那 么在 __getitem__
碰到找不到的键的时候,Python 就会自动调用它, 而不是抛出一个 KeyError 异常。
__missing__
方法只会被__getitem__
调用(比如在表达 式 d[k] 中)。提供__missing__
方法对 get 或者__contains__
(in 运算符会用到这个方法)这些方法的使用没有 影响。
1 |
|
定义了
__missing__
方法,在查不到值时转换为 str 重新查询
字典的变种
标准库里 collections 模块中,除了 defaultdict 之外还有其他的映射类型。
collections.OrderedDict
这个类型在添加键的时候会保持顺序,因此键的迭代次序总是一致 的。OrderedDict 的 popitem 方法默认删除并返回的是字典里的最后一个元素,但是如果像 my_odict.popitem(last=False) 这样调用 它,那么它删除并返回第一个被添加进去的元素。
collections.ChainMap
该类型可以容纳数个不同的映射对象,然后在进行键查找操作的时候,这些对象会被当作一个整体被逐个查找,直到键被找到为止。这个功能在给有嵌套作用域的语言做解释器的时候很有用,可以用一个映射 对象来代表一个作用域的上下文。在 collections 文档介绍 ChainMap 对象的那一部分 (https://docs.python.org/3/library/collections.html#collections.ChainMap) 里有一些具体的使用示例,其中包含了下面这个 Python 变量查询规则的 代码片段:
1 |
|
collections.Counter
这个映射类型会给键准备一个整数计数器。每次更新一个键的时候 都会增加这个计数器。所以这个类型可以用来给可散列表对象计数,或 者是当成多重集来用——多重集合就是集合里的元素可以出现不止一 次。Counter 实现了 + 和 - 运算符用来合并记录,还有像 most_common([n]) 这类很有用的方法。most_common([n]) 会按照次 序返回映射里最常见的 n 个键和它们的计数,详情参阅文档 (https://docs.python.org/3/library/collections.html#collections.Counter)。
- 下面的小例子利用 Counter 来计算单词中各个字母出现的次数:
1 |
|
colllections.UserDict
这个类其实就是把标准 dict 用纯 Python 又实现了一遍。 跟 OrderedDict、ChainMap 和 Counter 这些开箱即用的类型不 同,UserDict 是让用户继承写子类的。
- 就创造自定义映射类型来说,以 UserDict 为基类,总比以普通的 dict 为基类要来得方便。更倾向于从 UserDict 而不是从 dict 继承的主要原因是,后者有时 会在某些方法的实现上走一些捷径,导致我们不得不在它的子类中重写 这些方法,但是 UserDict 就不会带来这些问题。
- 另外一个值得注意的地方是,UserDict 并不是 dict 的子类,但是 UserDict 有一个叫作 data 的属性,是 dict 的实例,这个属性实际上 是 UserDict 最终存储数据的地方。这样做的好处是,UserDict 的子类就能在实现
__setitem__
的时候避免不必要的递 归,也可以让__contains__
里的代码更简洁。 - 下面用 userdict 重写 strkeydict
1 |
|
不可变映射类型
标准库里所有的映射类型都是可变的,但有时候你会有这样的需求,比如不能让用户错误地修改某个映射。
从 Python 3.3 开始,types 模块中引入了一个封装类名叫 MappingProxyType。如果给这个类一个映射,它会返回一个只读的映 射视图。虽然是个只读视图,但是它是动态的。这意味着如果对原映射 做出了改动,我们通过这个视图可以观察到,但是无法通过这个视图对 原映射做出修改。
- 用 MappingProxyType 来获取字典的只读实例:
1 |
|
参考资料
- 流畅的Python(2017年人民邮电出版社出版)
- https://docs.python.org/3/glossary.html#term-hashable
- https://baike.baidu.com/item/Hash/390310?fr=aladdin
文章链接:
https://www.zywvvd.com/notes/coding/python/fluent-python/chapter-3/python-dict/pytho-dict/
“觉得不错的话,给点打赏吧 ୧(๑•̀⌄•́๑)૭”
微信支付
支付宝支付