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. 键必须是可散列的
一个可散列的对象必须满足以下要求:
- 支持hash()函数,并且通过__hash__()方法所得到的散列值是不变的。
- 支持通过__eq__()方法来检测相等性。
- 若 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解释器都可能做出为字典扩容的决定。扩容的结果就是要新建一个更大的散列表,并把字典里已有的元素添加到新表里。这个过程中可能会发生新的散列冲突,导致新的散列表中键的次序变化。
不要对字典同时进行迭代和修改