0%

Fluent Python Chapter 3

第三章主要介绍的是字典(dict)和集合(set)类型。

Any running Python program has many dictionaries active at the same time, even if the user’s program coe doesn’t explicitly use a dictionary.

因为字典在Python自身中也被大量使用,因此被高度优化过了,其背后的实现就是哈希表。

创建字典

书中先给出了hashable的定义,然后列举了一些创建字典的常用方法:

1
2
3
4
5
6
7
>>> 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})
>>> a == b == c == d == e
True

字典推导

从Python2.7开始就支持了字典推导,方法与之前一章的列表推导几乎一样。

1
2
3
4
>>> DIAL_CODES = [(86, 'China'), (91, 'India), (1, 'United States)]
>>> country_code = {country: code for code, country in DIAL_CODES}
>>> country_code
{'China': 86, 'India': 91, 'United States': 1}

用setdefault处理key不存在

很多情况下我们会用d.get(k, default) 去取元素,这样可以避免去处理KeyError。但当我们要更新一个字典值的时候,用setdefault会更快:

1
my_dict.setdefault(key, []).append(new_value)

相当于:

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

因为setdefault会返回结果,如果是list的话,就可以直接append。这里就至少少了一次查找操作。

使用 defaultdict__missing__

defaultdict

collections中有个defaultdict, 允许在构造的时候传入工厂函数(factory function)作为一个default factory function。

例如:

1
2
3
4
5
6
>>> from collections import defaultdict
>>> dd = defaultdict(list)
>>> dd['new']
[]
>> dd
defaultdict(list, {'new': []})

如果在实例化dd的时候没有提供类似list这样的default factory function, 则在key missing的时候会报错(KeyError)

__missing__

在做映射的时候,如果没有这个key值, 则__getitem__会去调用__missing__方法。如果不存在__missing__,则抛出KeyError异常。

A better way to create a user-defined mapping type is to subclass collections.UserDict instead of dict.

Dict的各种变体(Variations)

书中总结了一下在标准库中各种dict的变体:

  1. collections.OrderedDict 保持了插入时候的顺序

  2. collections.ChainMap 这个感觉我没怎么用过,可以看官方文档的例子. 主要就是在合并多个dict的时候,会比不断调用update更快。其次是在创建嵌套(nested)内容的时候比较方便,在文档中也给出了一个例子。

  3. collections.Counter 对每一个key都有一个整数计数

    1
    2
    3
    4
    5
    >>> ct = collections.Counter('abracadabra')
    >>> ct
    Counter({'a':5, 'b':2, 'r':2, 'c':1, 'd':1})
    >>> ct.most_common(2)
    [('a', 5), ('z', 3)]
  4. collections.UserDict 主要是给用户自定义类做继承用的。UserDict并不是继承于Dict的。相比于继承dict的好处是比较纯粹,不需要我们去override一些builtin的方法。

不可变的映射

有时候我们希望在创建映射之后是不可变的,这时候可以用MappingProxyType. 可以通过查看types的文档

1
2
3
4
5
6
7
8
9
10
11
>>> from types import MappingProxyType
>>> d = {1:'A'}
>>> d_proxy = MappingProxyType(d)
>>> d_proxy
mappingproxy({1: 'A'})
>>> d_proxy[1]
'A'
>>> d_proxy[2] = 'x'
TypeError: 'mappingproxy' object does not support item assignment
>>> d[2] = 'B'
mappingproxy({1: 'A', 2: 'B'})

如何创建集合更快

通常有两种方式创建一个新的集合:

  1. {1}

  2. set([1])

    这里第一种方式会更加快一些,第一种中Python使用了BUILD_SETbytecode去操作。书中给出了两种方式的bytecode比较。

dictsethashtable实现方式

书中在最后一部分给出了两种数据结构的性能实验和具体利用hash table的实现方式,其中也解释了为何因为追求速度的原因使得它们是乱序的。