《Fluent Python》笔记 字典和集合

用setdefault处理找不到的键

对于如下两段代码,所实现的功能都是一样的。

1
2
3
if key not in my_dict:
my_dict[key] = []
my_dict[key].append(new_value)
1
my_dict.setdefault(key, []).append(new_value)

相较而言,使用setdefault来处理字典中不存在的键时,更加优雅。

用defaultdict处理找不到的键

defaultdict是一个字典类型。对于上面代码中解决的键不存在问题,我们可以用defaultdict类型解决。

1
2
my_dd = collections.defaultdict(list)
my_dd[key].append(new_value)

当表达式my_dd[key]中键key不存在时,会进行如下步骤:

(1) 调用list()来建立一个新列表。
(2) 把这个新列表作为值, key作为它的键, 放到my_dd中。
(3) 返回这个列表的引用。

特殊方法_missing_

所有的映射类型(如dict)在处理不存在的键时,都会调用到__missing__方法。

下面这个示例是定义了一个StrKeyDict0类,在查询过程中会将非字符串的键转换成字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# BEGIN STRKEYDICT0
class StrKeyDict0(dict): # 继承了dict

def __missing__(self, key):
if isinstance(key, str): # 如果不存在的键是字符串,抛出异常
raise KeyError(key)
return self[str(key)] # 如果不存在的键不是字符串,将其转换成字符串后再进行查询

def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default

def __contains__(self, key):
return key in self.keys() or str(key) in self.keys()

# END STRKEYDICT0

代码中__missing__方法只会被__getitem__方法调用,即当d[k]k键不存在时会被调用。而get 或者__contains__(in 运算符会用调用这个方法) 这些方法遇到不在存在键时则不会被调用。 所以为了能满足程序需求,上述代码中重写了get__contains__方法,使其能够应对键为非字符串的情况。

字典的变种

  • collections.defaultdict

    在构造时可以提供一个可调用对象(如上述代码中的list),当d[k]k不存在时会被调用并返回某种默认值(如 k:[])。

  • collections.OrderedDict

    这个类型在添加键的时候会保持顺序, 因此键的迭代次序总是一致的。 OrderedDictpopitem 方法默认删除并返回的是字典里的最后一个元素, 但是如果像 my_odict.popitem(last=False) 这样调用它, 那么它删除并返回第一个被添加进去的元素。

  • collections.ChainMap

    这个类型可以容纳数个不同的映射对象, 然后在进行键查找操作的时候,这些对象会被当作一个整体被逐个查找,直到键被找到为止。

  • collections.Counter

    这个类型会给键准备一个整数计数器。 每次更新一个键的时候都会增加这个计数器。most_common([n]) 会按照次序返回映射里最常见的 n 个键和它们的计数。

    1
    2
    3
    4
    5
    6
    7
    8
    >>> ct = collections.Counter('abracadabra')
    >>> ct
    Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
    >>> ct.update('aaaaazzz')
    >>> ct
    Counter({'a': 10, 'z': 3, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
    >>> ct.most_common(2)
    [('a', 10), ('z', 3)]
  • colllections.UserDict

    OrderedDict、 ChainMap 和 Counter 这些开箱即用的类型不同, UserDict 是让用户继承写子类的。当我们需要创建自定义映射类型时,通过继承UserDict类比继承dict更方便。

不可变映射类型

标准库中的所有映射类型都是可变的,即可以修改某个映射。在types模块中引入了一个封装类名叫MappingProxyType,一个映射对象通过这个类后,会得到一个只读的映射视图(无法通过映射视图修改原映射,除非直接对原映射进行修改)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> from types import MappingProxyType
>>> d = {1:'A'}
>>> d_proxy = MappingProxyType(d)
>>> d_proxy
mappingproxy({1: 'A'})
>>> d_proxy[1]
'A'
>>> d_proxy[2] = 'x'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'mappingproxy' object does not support item assignment
>>> d[2] = 'B'
>>> d_proxy
mappingproxy({1: 'A', 2: 'B'})
>>> d_proxy[2]
'B'
>>>

集合

集合本质是许多唯一对象的聚集。因此集合可用于序列对象的去重。

集合中元素必须是可散列的。set类型本身是不可散列的,frozenset类型本身是可散列的。

给定两个集合ab返回的是它们的合集, 得到的是交集, 而 a - b得到的是差集。

由于散列表的引入,集合的查找功能非常快。

集合的字面量形如{1,2,3},空集必须写成set()而非{}(Python会认为{}是空字典)。

集合操作

语法 描述
a & b 返回集合a和集合b的交集
a | b 返回集合a和集合b的并集
a - b 返回集合a和集合b的差集
a ^ b 返回集合a和集合b的对称差集
a.isdisjoint(b) 查看集合a和集合b是否不相交(没有共同元素)
c in a 元素c是否属于集合a
a <= b / a >= b 集合a是否为集合b的子集/父集
a < b / a > b 集合a是否为集合b的真子集/真父集

其他方法:a.add(c)、a.clear()、a.copy()、a.discard(c)(如果 a 里有 c 这个元素的话, 把它移除)、a.pop()、a.remove(c)等

a、b为集合,c为元素。