字典和集合
散列
- 如果一个对象是可散列的,那么在这个对象的生命周期中,它的散列值是不变的,而且这个对象需要实现
__hash__()方法。另外可散列对象还要有__qe__()方法,这样才能跟其他键做比较。如果两个可散列对象是相等的,那么它们的散列值一定是一样的。 - 只有当一个元组所包含的所有元素都是可散列类型的情况下,它才是可散列的。
- 散列表其实是一个稀疏数组(总是有空白元素的数组称为稀疏数组),所以字典、集合在空间上的效率低下。因此如果需要存放数量巨大的记录,那么放在自由元组或是具名元组构成的列表中会是比较好的选择;最好不要根据
JSON的风格用由字典组成的列表来存放这些记录。一是避免了散列表的空间损耗,二是没有必要把每个字段的名字都存一遍。
字典推导
>>> DIAL_CODES = [
... (86, 'China'),
... (91, 'India'),
... (1, 'United States'),
... (62, 'Indonesia'),
... (55, 'Brazil'),
... (92, 'Pakistan'),
... (880, 'Bangladesh'),
... (234, 'Nigeria'),
... (7, 'Russia'),
... (81, 'Japan'),
... ]
>>> country_code = {country: code for code, country in DIAL_CODES}
>>> country_code
{'China': 86, 'India': 91, 'Bangladesh': 880, 'United States': 1,
'Pakistan': 92, 'Japan': 81, 'Russia': 7, 'Brazil': 55, 'Nigeria':
234, 'Indonesia': 62}
>>> {code: country.upper() for country, code in country_code.items()
... if code < 66}
{1: 'UNITED STATES', 55: 'BRAZIL', 62: 'INDONESIA', 7: 'RUSSIA'}
常见映射方法
| dict | defaultdict | OrderedDict | ||
|---|---|---|---|---|
d.clear() | • | • | • | 移除所有元素 |
d.__contains__(k) | • | • | • | 检查k是否在d中 |
d.copy() | • | • | • | 浅复制 |
d.__copy__() | • | 用于支持copy.copy | ||
d.default_factory | • | 在__missing__函数中被调用的函数,用以给未找到的元素设置值 | ||
d.__delitem__(k) | • | • | • | del d[k], 移除键为k的元素 |
d.fromkeys(it, [initial]) | • | • | • | 将迭代器it里的元素设置为映射里的键,如果有initial参数,就把它作为这些键对应的值(默认值是None) |
d.get(k, [default]) | • | • | • | 没有键 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, [default]) | • | • | • | 返回键k所对应的值,然后移除这个键值对,如果没有这个键,返回None或者default |
d.popitem() | • | • | • | 随机返回一个键值对并从字典里移除它 |
d.__reversed__() | • | 返回倒序的键的迭代器 | ||
d.setdefault(k, [default]) | • | • | • | 若字典里有键 k则返回对应的值;若无则d[k] = default,然后返回default |
d.__setitem__(k, v) | • | • | • | 实现d[k] = v操作,把对应的值设为 v |
d.update(m, [**kargs]) | • | • | • | m 可以是映射或者键值对迭代器,用来更新 d 里对应的条目 |
d.values() | • | • | • | 返回字典里所有的值 |
-
d.update(m, [**kargs])该方法处理参数
m的方式是典型的“鸭子类型”。函数首先检查m是否有keys方法,如果有,那么update函数就把它当作映射对象来处理。否则,函数会退一步,转而把m当作包含了键值对(key, value)元素的迭代器。In [24]: _dict = dict.fromkeys(range(10), "value") In [25]: _dict Out[25]: {0: 'value', 1: 'value', 2: 'value', 3: 'value', 4: 'value', 5: 'value', 6: 'value', 7: 'value', 8: 'value', 9: 'value'} In [26]: _dict.update(zip(range(100, 110), range(200, 210))) In [27]: _dict Out[27]: {0: 'value', 1: 'value', 2: 'value', 3: 'value', 4: 'value', 5: 'value', 6: 'value', 7: 'value', 8: 'value', 9: 'value', 100: 200, 101: 201, 102: 202, 103: 203, 104: 204, 105: 205, 106: 206, 107: 207, 108: 208, 109: 209} -
setdefault(k, [default])为字典更新键值对时,使用
setdefault是比较好的办法。my_dict.setdefault(key, []).append(new_value) #方法一 if key not in my_dict: #方法二 my_dict[key] = [] my_dict[key].append(new_value)方法一、二的效果是一样的,不过后者至少要进行两次键查询——如果键不存在的话就是三次,而使用
setdefault只需要一次就可以完成整个操作。 -
d.default_factory在创建一个
defaultdict时接受一个方法作为default_factory。default_factory只会在__getitem__里被调用,在其他的方法里完全不会发挥作用。例如:k 是个找不到的键,d[k]这个表达式会调用default_factory创造某个默认值,而d.get(k)则会返回None。背后的原理为,在找不到键时__getitem__会调用__missing__,而__missing__则会调用default_factory方法。 -
__missing__所有的映射类型在处理找不到的键的时候,都会牵扯到
__missing__方法。这也是这个方法称作“missing”的原因。虽然基类dict并没有定义这个方法,但是dict是知道有这么个东西存在的。也就是说,如果有一个类继承了dict,然后这个继承类提供了__missing__方法,那么在__getitem__碰到找不到的键的时候,Python 就会自动调用它,而不是抛出一个 KeyError 异常。
字典的变种
-
collections.Counter>>> 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)] -
collections.UserDict这个类其实就是把标准
dict用纯 Python 又实现了一遍。跟OrderedDict、ChainMap和Counter这些开箱即用的类型不同,UserDict是让用户继承写子类的。
子类化UserDict
更倾向于从 UserDict 而不是从 dict 继承的主要原因是,后者有时会在某些方法的实现上走一些捷径,导致我们不得不在它的子类中重写这些方法,但是 UserDict 就不会带来这些问题。
另一个值得注意的地方是,UserDict并不是dict的子类,但是UserDict有个一叫作data的属性(只有在实例化一个对象后,对象才会有data属性),即如下所示:该属性是dict的实例,这个属性实际上是UserDict最终存储数据的地方。
In [33]: set(dir(collections.UserDict())) - set(dir(collections.UserDict))
Out[33]: {'data'}
不可变映射类型
从 Python 3.3 开始,types 模块中引入了一个封装类名叫MappingProxyType。如果给这个类一个映射,它会返回一个只读的映射视图。虽然是个只读视图,但是它是动态的。这意味着如果对原映射做出了改动,我们通过这个视图可以观察到,但是无法通过这个视图对原映射做出修改。
>>> 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可以,因此可以创建一个包含不同frozenset的set。
-
句法陷阱
如果要创建一个空集,必须用不带任何参数的构造方法
set()。如果只是写成{}的形式,创建的实际上是一个空字典。 -
构建速度
{}快于set([])- 像
{1, 2, 3}这种字面量句法相比于构造方法(set([1, 2, 3]))要更快且更易读。后者的速度要慢一些,因为 Python 必须先从set这个 名字来查询构造方法,然后新建一个列表,最后再把这个列表传入到构造方法里。但是如果是像{1, 2, 3}这样的字面量,Python 会利用一个专门的叫作BUILD_SET的字节码来创建集合。
-
集合推导
略:懂得都懂
-
集合的特点
- 集合里的元素必须是可散列的。
- 集合很消耗内存。
- 可以很高效地判断元素是否存在于某个集合。
- 元素的次序取决于被添加到集合里的次序。
- 往集合里添加元素,可能会改变集合里已有元素的次序。