文章目录
- 3.1 泛映射类型
- 什么是可散列的数据类型(键的要求)
- 字典的构造方法
- 3.2 字典推导(dictcomp)
- 3.3 常见的映射方法
- 用setdefault处理找不到的键
- 3.4 映射的弹性键查询
- 3.4.1 defaultdict:处理找不到的键的一个选择
- 注意:
- defaultdict与dict实例化字典类型的区别
- defaultdict的构造
- 3.4.2 特殊方法__missing__
- 3.5 字典的变种
- collections.OrderedDict (添加键会保持顺序)
- collections.ChainMap(将多个映射合并为单个映射)
- collections.Counter
- 例子:统计单词中各个字母出现的次数
- collections.UserDict
- 3.6 子类化UserDict
- MutableMapping.update
- Mapping.get
- 从dict或者其他内置类继承有什么不好?
- 3.7 不可变映射类型(动态的只读的映射视图:MappingProxyType)
3.1 泛映射类型
collections.abc模块有Mapping和MutableMapping 这两个抽象基类,它们的作用是为了dict和其他类似的类型定义形式接口,然后非抽象映射类型一般不会直接继承这些抽象基类,它们会直接对dict或者collections.UserDict进行扩展。这些抽象基类的主要作用是作为形式化的文档,它们定义了构建一个映射类型所需要的最基本的接口。然后它们还可以跟isinstance一起被用来判定某个数据是不是广义上的映射类型:
>>> from collections import abc
>>> my_dict={} # 字典是典型的键值对
>>> isinstance(my_dict,abc.Mapping)
True
>>> isinstance([1, 2], abc.Mapping)
False #列表时序列
>>> isinstance((1, 2), abc.Mapping)
False #元组也是序列
>>> isinstance('sdbd', abc.Mapping)
False #字符串也是序列
- 这里用isintance而不是type来检查某个参数是否为dict类型,因为这个参数有可能不是dict,而是一个比较另类的映射类型。(这句话不太明白)
- 标准库里的所有映射类型都是利用dict来实现的,因此它们有个共同的限制,即只有可散列的数据类型才可以用作这些映射里的键(只有键有这个要求,值没有此要求)
什么是可散列的数据类型(键的要求)
如果一个对象是可散列的,那么在这个对象的生命周期中,它的散列值是不变的而且这个对象需要实现__hash__()方法。另外可散列对象还要有__eq__()方法,这样才能和其他键作比较。如果两个散列对象是相等的,那么它们的散列值一定是一样
的。
可散列类型包括:
- (1)原子不可变类型(str, bytes和数值类型)
- (2)frozenset
- (3)元组:只有当元组包含的所有元素都是可散列的情况下。
可以用句话说:python里所有的不可变类型都是可散列的
一般来讲用户自定义的类型的对象都是可散列的,散列值就是它们的id()函数的返回值。
字典的构造方法
>>> 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({'one':1,'two':2,'three':3})
>>> e=dict([('two',2),('one',1),('three',3)])
>>> a == b ==c == d == e
True
- 注意这里的相等,只不过是值相等,但是不同的对象
3.2 字典推导(dictcomp)
列表生成器和生成器表达式的概念已经移植到了字典上,从而有了字典推导。
字典推导可以从任何键值对作为元素的可迭代对象中构建出字典。
例子:
>>> 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, 'United States': 1, 'Indonesia': 62, 'Brazil': 55, 'Pakistan': 92, 'Bangladesh': 880, 'Nigeria': 234, 'Russia': 7, 'Japan': 81}
>>> {code:country.upper() for country,code in country_code.items() if code <66}
{1: 'UNITED STATES', 62: 'INDONESIA', 55: 'BRAZIL', 7: 'RUSSIA'}
字典推导的表达式会蔓延到其他数据结构类型
3.3 常见的映射方法
除了
- dict
- defaultdict
- OrderedDict
这三种常见方法
在映射对象的方法里,setdefault可能是比较微妙的一个。尽管用的次数不多,但是它一旦发挥作用,就可以节省不少次键查询,让程序更高效。
用setdefault处理找不到的键
我们可以使用d.get(k,default)来代替d[k],给找不到的键一个默认的返回值(这比处理keyError方便不少)
看个例子:
>>> my_dict = {'子': '鼠', '丑': '牛', '寅': '虎',
... '卯': '兔', '辰': '龙', '巳': '蛇',
... '午': '马', '未': '羊', '申': '猴',
... '酉': '鸡', '戌': '狗', '亥': '猪'}
>>> my_dict.setdefault('子','属鼠') # 显然键 '子'存在,那么 值 '属鼠' 也就无用
'鼠'
>>> my_dict.setdefault('行初心','CSDN') # 如果找不到,就会添加.
'CSDN'
>>> my_dict.setdefault('行') # 不存在的键"行",未指定值,默认返回None
>>> my_dict
{'子': '鼠', '丑': '牛', '寅': '虎', '卯': '兔', '辰': '龙', '巳': '蛇', '午': '马', '未': '羊', '申': '猴', '酉': '鸡', '戌': '狗', '亥': '猪', '行初心': 'CSDN', '行': None}
例子2:(使用dict.setdefault()方法来设置默认值,统计字符串出现的次数)
strings = ('puppy', 'kitten', 'puppy', 'puppy','weasel', 'puppy', 'kitten', 'puppy')
counts = {}
for kw in strings:counts.setdefault(kw, 0)counts[kw] += 1
dict.setdefault()方法的返回值可以重写for循环中的代码,使其更加简洁:
strings = ('puppy', 'kitten', 'puppy', 'puppy','weasel', 'puppy', 'kitten', 'puppy')
counts = {}
for kw in strings:counts[kw] = counts.setdefault(kw, 0) + 1
3.4 映射的弹性键查询
为了方便,就算某个键在映射里不存在,那么你也希望在通过这个键读取值的时候能得到一个默认值。有两个途径帮我们达到这个目的。
- (1).通过defaultdict这个类型而不是普通的dict
- (2).给自己定义一个dict类型的子类,然后在这个子类中实现__missing__方法。
3.4.1 defaultdict:处理找不到的键的一个选择
在用户创建defaultdict对象的时候,就需要给它配置一个为找不到的键创造默认值的方法。
具体而言,在实例化一个defaultdict的时候,需要给构造方法提供一个可调用的对象,这个可调用对象会在__getitem__碰到找不到的键的时候被调用,让__getitem__返回某种默认值。
比如,新建一个字典:dd=defaultdict(list),如果键’new-key’在dd中不存在的话,表达式dd[‘new-key’]会按照以下步骤行事。
- (1).调用list()来创建新列表
- (2).把这个新列表作为值,'new-key’作为它的键,放到dd中。
- (3).返回这个列表的引用
而这个用来生成默认值的可调用对象存放在名为default_factory的实例属性里。
如果在创建defaultdict的时候没有指定default_factory,查询不存在的键会触发KeyError.
注意:
- defaultdict里面的default_factory只会在__getitem__里被调用,在其他的方法里完全不会发挥作用。比如,dd是个defaultdict,K是个找不到的键,dd[k]这个表达式会调用default_factory创造某个默认值,而dd.get(k)则会返回None.
所有 这一切背后的功臣其实是特殊方法__missing__.它会在defaultdict遇到找不到的键的时候调用default_factory,而实际上这个特性是所有映射类型都可以去选择的。
看个例子:
from collections import defaultdict
class from_defaultdict(defaultdict):def __getitem__(self, key):return 'hello'c = from_defaultdict(list)print(c['new-key'])
结果如下:
defaultdict与dict实例化字典类型的区别
使用defaultdict任何未定义的key都会默认返回一个根据method_factory参数不同的默认值, 而相同情况下dict()会返回KeyError.
比较下面代码:
from collections import defaultdict
d1 = dict()
d2 = defaultdict(list)
print(d2['a'])
print(d1['a'])
输出:
[]
Traceback (most recent call last):File "/home/maxzhang/PycharmProjects/pythoncode/t.py", line 5, in <module>print(d1['a'])
KeyError: 'a'
defaultdict的构造
python官方文档中对defaultdict的定义如下:
class collections.defaultdict([default_factory[, ...]])
python官方文档中对defaultdict的解释如下:
defaultdi:
dict subclass that calls a factory function to supply missing values
- default_factory 接收一个工厂函数作为参数, 例如int str,list,set等.
- defaultdict在dict的基础上添加了一个__missing__(key)方法, 在调用一个不存的key的时候, defaultdict会调用__missing__, 返回一个根据default_factory参数的默认值, 所以不会返回Keyerror.
3.4.2 特殊方法__missing__
所有的映射类型在处理找不到的键的时候,都会牵扯到__missing__方法。这也是和这个方法称作’missing’的原因。虽然基类dict并没有定义这个方法,但是dict是知道有这么一个东西存在的。也就是说,如果有一个类继承了dict,然后这个继承类提供了__missing__方法,那么在__getitem__碰到找不到的键的时候,python会自动调用它。而不是抛出异常。
- 注意:__missing__方法只会被__getitem__调用。提供__missing__方法对get或者__contains__这些方法的使用没有影响。
如果要自定义一个映射类型,更合适的策略是继承collections.UserDict类。
3.5 字典的变种
collections.OrderedDict (添加键会保持顺序)
这个类型在添加键的时候会保持顺序,因此键的迭代次序总是一致的。OrderedDict的popitem方法默认删除并返回的是字典里的最后一个元素,但是如果像my_odict.popitem(last=False)这样调用它,那么它删除并返回第一个被添加进去的元素。
例子:
>>> d = collections.OrderedDict()
>>> d['a'] = 'A'
>>> d['b'] = 'B'
>>> d['c'] = 'C'
>>> for k ,v in d.items():
... print(k,v)
...
a A
b B
c C
>>> d.popitem(last=False)
('a', 'A')
>>> d
OrderedDict([('b', 'B'), ('c', 'C')])
collections.ChainMap(将多个映射合并为单个映射)
该类型可以容纳数个不同的映射对象,然后在进行键查找操作的时候,这些对象会被当做一个整体逐个查找,直到键被找到为止。
例子:
>>> import collections
>>> a = {'x': 1, 'z': 3}
>>> b = {'y': 2, 'z': 4}
>>> c = collections.ChainMap(a, b)
>>> c['x']
1
collections.Counter
这个映射会给键准备一个整数计数器。每次更新一个键的时候都会增加这个基数器。所以这个类型可以用来给可散列表对象计数,或者是当成多重集来使用—多重集合就是集合里的元素可以出现不止一次。Counter实现了+和-运算符用来合并记录,还有像most_common([n])这类很有用的方法。most_common([n])会按照次序返回映射里最常见的n个键和它们的计数。
例子:统计单词中各个字母出现的次数
>>> ct = collections.Counter('afalfjlahgksdadaa')
>>> ct
Counter({'a': 6, 'f': 2, 'l': 2, 'd': 2, 'j': 1, 'h': 1, 'g': 1, 'k': 1, 's': 1})
>>> ct.update('aaaaaaaaadffdwe')
>>> ct
Counter({'a': 15, 'f': 4, 'd': 4, 'l': 2, 'j': 1, 'h': 1, 'g': 1, 'k': 1, 's': 1, 'w': 1, 'e': 1})
>>> ct.most_common(2)
[('a', 15), ('f', 4)]
collections.UserDict
这个类其实就是把标准dict用纯python又实现了一遍
UserDict就是让用户继承写子类的。
3.6 子类化UserDict
就创造自定义映射类型来说,以UserDict为基类,总比以普通的dict为基类要来的方便。
更倾向于从UserDict而不是从dict继承的主要原因是,后者有时会在某些方法的实现上走一些捷径,导致我们不得不在它的子类中重写这些方法,但是UserDict就不会带来这些问题。
另外一个值得注意的地方是,UserDict并不是dict的子类,但是UserDict有一个叫做data的属性是dict的实例,这个属性就是UserDict最终存储数据的地方。
例子:
import collectionsclass StrKeyDict(collections.UserDict):def __missing__(self, key):if isinstance(key,str):raise KeyErrorreturn self[str(key)]def __contains__(self, key):return str(key) in self.datadef __setitem__(self, key, item):self.data[str(key)] = item
因为UserDict 继承的是MutableMapping,所以StrKeyDict里剩下的的那些映射类型的方法都是从UserDict,MutableMapping和Mapping这些超类继承而来的。特别是最后的Mapping类,它虽然是一个抽象类(ABC),但是它提供了许多使用的方法。
MutableMapping.update
这个方法不但可以直接利用,它还用在__init__里,让构造方法可以利用传入的各种参数(其他映射类型,元素是(key,value)对的可迭代对象和键值参数)来新建实例。因为这个方法在背后是使用self[key]=value来添加新值的,所以它其实是在使用我们的__setitem__方法
Mapping.get
从dict或者其他内置类继承有什么不好?
3.7 不可变映射类型(动态的只读的映射视图:MappingProxyType)
标准库里所有的映射类型都是可变的,但是有时候你会有这样的需求,比如不能让用户错误修改某个映射。
在types模块中引入了一个封装类名叫MappingProxyType。如果给这个类一个映射,它会返回一个动态的只读的映射视图。如果对原映射做出了修改,这个视图可以观察到,但是无法通过这个视图对原映射进行修改。
例子:
>>> from types import MappingProxyType
>>> d = {1:'A'}
>>> d_proxy = MappingProxyType(d)
>>> d_proxy
mappingproxy({1: 'A'})
>>> d_proxy[1] #d中的内容可以通过d_proxy看到
'A'
>>> d_proxy[2] = 'x' #但是d_proxy不能做任何的修改
Traceback (most recent call last):File "<stdin>", line 1, in <module>
TypeError: 'mappingproxy' object does not support item assignment
>>> d[2]= 'B'
>>> d_proxy #d_proxy是动态的,也就是说对d所做的任何改动都会反馈到它上面
mappingproxy({1: 'A', 2: 'B'})
>>> d_proxy[2]
'B'
>>>