《流畅的Python》读书笔记5(第三章:字典和集合)

156 阅读3分钟

3.8 集合论

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

>>> L = ['spam', 'spam', 'egg', 'spam']

>>> set(L)

{'spam', 'egg'}

>>> list(set(L))

['spam', 'egg']

集合实现了很多基础的中缀运算符。给定两个集合a和b,a | b返回它们的合集, a & b 得到交集, a - b 得到差集.

3.8.1 集合字面量

集合的字面量————{1}, {1, 2}.如果是空集,必须写成set()

>>> s = {1}

>>> type(s)

<class 'set'>

>>> s

{1}

>>> s.pop()

1

>>> s

set()

3.8.2 集合推导

>>> {chr(i) for i in range(32, 42)}

{' ', '&', '%', '(', '!', ')', '#', "'", '"', '$'}

3.8.3 集合的操作

docs.python.org/3/library/s…

3.9 dict和set的背后

要想理解Python里字典和集合的长处与弱点,它们背后的散列表是绕不开的一环。

3.9.1 一个关于效率的实验

>>> import time

>>> MAX = 10_000_000

>>> list_a = [i for i in range(MAX)]

>>> set_a = set(list_a)

# list_a的每个值作为key,value为None
>>> dict_a = {}.fromkeys(list_a)


>>> test = [i for i in range(1000)]

>>> def testTime(x, name):

...     start = time.clock()

...     for i in test:

...         if i in x:

...             pass

...     print(name, ':', '%f'%(time.clock()-start))

...

>>> testTime(list_a,'list')

list : 0.019038

>>> testTime(set_a,'set')

set : 0.000276

>>> testTime(dict_a, 'dict')

dict : 0.000313

3.9.2 字典中的散列表

散列表是一个稀疏数组。在一般的数据结构教材中,散列表里的单元通常叫作表元。在dict的散列表中,每个键值对都占有一个表元,每个表元有两个部分,一个是对键的引用,另一个是对值得引用。因为所有表元的大小一致,所以可以通过偏移量读取某个表元。

1.散列值的想等性

内置的hash()方法可以用于所有内置类型对象。如果是自定义对象调用hash(),实际上是运行自定义的__hash__.如果两个对象比较的时候是相等的,那它们的散列值必须相等。

2.散列值算法

为了获取my_dict[search_key]背后的值,Python会首先调用hash(search_key)来计算search_key的散列值,把这个值最低的几位数字当作偏移量,在散列表里查找表元。若找到的表元是空的,则抛出KeyError异常。若不是空的,则表元里会有一对found_key:found_value。这时候Python会检验search_key == found_key是否为真,如果它们相等的话,就会返回found_value。

如果search_key与found_key不相等的话,这种情况称为散列冲突。

3.9.3 dict的实现及其导致的结果

1. 键必须是可散列的

一个可散列的对象必须满足以下要求:

  1. 支持hash()函数,并且通过__hash__()方法所得到的散列值是不变的。
  2. 支持通过__eq__()方法来检测相等性。
  3. 若 a==b 为真,则hash(a) == hash(b)也为真。

2. 字典在内存上的开销大

由于字典使用了散列表,而散列表又必须是稀疏的,这导致它在空间上的效率低下。

3.键查询很快

dict的实现是典型的空间换时间:字典类型有着巨大的内存开销,但它们提供了无视数据量大小的快速访问。

4. 键的次序取决于添加顺序

>>> DIAL_CODES = [

... (86, 'China'),

... (91, 'India'),

... (1, 'USA'),

... (62, 'Indonesia'),

... (55, 'Brazil'),

... (92, 'Pakistan'),

... (880, 'Bangladesh'),

... (234, 'Nigeria'),

... (7, 'Russia'),

... (81, 'Japan'),

... ]

>>> d1 = dict(DIAL_CODES)

>>> print("d1:", d1.keys())

d1: dict_keys([86, 91, 1, 62, 55, 92, 880, 234, 7, 81])

>>> d2 = dict(sorted(DIAL_CODES))

>>> print('d2:', d2.keys())

d2: dict_keys([1, 7, 55, 62, 81, 86, 91, 92, 234, 880])

>>> d3 = dict(sorted(DIAL_CODES, key=lambda x:x[1]))

>>> print('d3:', d3.keys())

d3: dict_keys([880, 55, 86, 91, 62, 81, 234, 92, 7, 1])

>>> assert(d1 == d2 and d2 == d3)

5. 往字典里添加新键可能会改变已有键的顺序

无论何时往字典里添加新的键,Python解释器都可能做出为字典扩容的决定。扩容的结果就是要新建一个更大的散列表,并把字典里已有的元素添加到新表里。这个过程中可能会发生新的散列冲突,导致新的散列表中键的次序变化。

不要对字典同时进行迭代和修改