Python 字典和集合

泛映射类型

  collections.abc 模块中有 Mapping 和 MutableMapping 这两个抽象基类,它们的作用是为 dict 和其他类似的类型定义形式接口(在Python 2.6 到 Python 3.2 的版本中,这些类还不属于 collections.abc模块,而是隶属于 collections 模块)。详见图

 

collections.abc 中的 MutableMapping 和它的超类的UML 类图(箭头从子类指向超类,抽象类和抽象方法的名称以斜体显示)

  然而,非抽象映射类型一般不会直接继承这些抽象基类,它们会直接对dict 或是 collections.User.Dict 进行扩展。这些抽象基类的主要作用是作为形式化的文档,它们定义了构建一个映射类型所需要的最基本的接口。然后它们还可以跟 isinstance 一起被用来判定某个数据是不是广义上的映射类型:

>>> my_dict = {}
>>> isinstance(my_dict, abc.Mapping)
True

  原子不可变数据类型(str、bytes 和数值类型)都是可散列类型,frozenset 也是可散列的,因为根据其定义,frozenset 里只能容纳可散列类型。元组的话,只有当一个元组包含的所有元素都是可散列类型的情况下,它才是可散列的。来看下面的元组tt、tl 和 tf:

>>> tt = (1, 2, (30, 40))
>>> hash(tt)
8027212646858338501
>>> t1 = (1, 2, [30, 40])
>>> hash(t1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> tf = (1, 2, frozenset([30, 40]))
>>> hash(tf)
-4118419923444501110

举个? 来说明创建字典的不同方式:

>>> 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

 

字典推导

字典推到式的应用

 1 DIAL_CODES = [
 2     (86, 'China'),
 3     (91, 'India'),
 4     (1, 'United States'),
 5     (62, 'Indonesia'),
 6     (55, 'Brazil'),
 7     (92, 'Pakistan'),
 8     (880, 'Bangladesh'),
 9     (234, 'Nigeria'),
10     (7, 'Russia'),
11     (81, 'Japan'),
12 ]
13 
14 country_code = {country: code for code, country in DIAL_CODES}
15 print(country_code)
16 
17 #过滤国家编号小于66的所有信息
18 r1 = {country: code for country, code in country_code.items() if code < 66 }
19 print(r1)
20 
21 r2 = filter(lambda x:x[1] < 66, country_code.items())
22 print(dict(r2))

以上代码执行的结果为:

{'China': 86, 'India': 91, 'United States': 1, 'Indonesia': 62, 'Brazil': 55, 'Pakistan': 92, 'Bangladesh': 880, 'Nigeria': 234, 'Russia': 7, 'Japan': 81}
{'United States': 1, 'Indonesia': 62, 'Brazil': 55, 'Russia': 7}
{'United States': 1, 'Indonesia': 62, 'Brazil': 55, 'Russia': 7}

用setdefault处理找不到的键

  当字典 d[k] 不能找到正确的键的时候,Python 会抛出异常,这个行为符合 Python 所信奉的“快速失败”哲学。也许每个 Python 程序员都知道可以用 d.get(k, default) 来代替 d[k],给找不到的键一个默认的返回值(这比处理 KeyError 要方便不少)。但是要更新某个键对应的值的时候,不管使用 __getitem__ 还是 get 都会不自然,而且效率低。

  举个? index0.py 这段程序从索引中获取单词出现的频率信息,并把它们写进对应的列表里 

 1 import sys
 2 import re
 3 
 4 WORD_RE = re.compile(r'\w+')
 5 
 6 index = {}
 7 with open(sys.argv[1], encoding='utf-8') as fp:
 8     for line_no, line in enumerate(fp, 1):
 9         for match in WORD_RE.finditer(line):
10             word = match.group()
11             column_no = match.start() + 1
12             location = (line_no, column_no)
13             # 这其实是一种很不好的实现,这样写只是为了证明论点
14             occurrences = index.get(word, [])   #提取 word 出现的情况,如果还没有它的记录,返回[]
15             occurrences.append(location)        #把单词新出现的位置添加到列表的后面
16             index[word] = occurrences           #把新的列表放回字典中,这又牵扯到一次查询操作
17 # 以字母顺序打印出结果                 #排序的时候,单词会被规范成统一格式
18 for word in sorted(index, key=str.upper):
19     print(word, index[word])

以上代码执行的结果为:

$ python3 index0.py ../../data/zen.txt
a [(19, 48), (20, 53)]
Although [(11, 1), (16, 1), (18, 1)]
ambiguity [(14, 16)]
and [(15, 23)]
are [(21, 12)]
aren [(10, 15)]
at [(16, 38)]
bad [(19, 50)]
be [(15, 14), (16, 27), (20, 50)]
beats [(11, 23)]
Beautiful [(3, 1)]
better [(3, 14), (4, 13), (5, 11), (6, 12), (7, 9), (8, 11),
(17, 8), (18, 25)]
...

index.py 用一行就解决了获取和更新单词的出现情况列表,当然跟示例 3-2 不一样的是,这里用到了 dict.setdefault

 1 import sys
 2 import re
 3 
 4 
 5 WORD_RE = re.compile(r'\w+')
 6 
 7 index = {}
 8 with open(sys.argv[1], encoding='utf-8') as fp:
 9     for line_no, line in enumerate(fp, 1):
10         for match in WORD_RE.finditer(line):
11             word = match.group()
12             column_no = match.start() + 1
13             location = (line_no, column_no)
14             '''
15             获取单词的出现情况列表,如果单词不存在,把单词和一个空列表放进映射,然后返回这个空列表,
16             这样就能在不进行第二次查找的情况下更新列表了
17             '''
18             index.setdefault(word, []).append(location)
19 
20 # 以字母顺序打印出结果
21 for word in sorted(index, key=str.upper):
22     print(word, index[word])
23     

  获取单词的出现情况列表,如果单词不存在,把单词和一个空列表放进映射,然后返回这个空列表,这样就能在不进行第二次查找的情况下更新列表了。

  也就是说,这样写:

 1 my_dict = {'name': 'demon'}
 2 my_dict.setdefault('course', []).append('python')
 3 
 4 print(my_dict)
 5 
 6 #等同于上面的写法,不过这个要查询两次
 7 if 'course' not in my_dict:
 8     my_dict['course'] = []
 9 my_dict['course'].append('golang')
10 print(my_dict)

二者的效果是一样的,只不过后者至少要进行两次键查询——如果键不存在的话,就是三次,用 setdefault 只需要一次就可以完成整个操作。 

特殊方法__missing__

  所有的映射类型在处理找不到的键的时候,都会牵扯到 __missing__方法。这也是这个方法称作“missing”的原因。虽然基类 dict 并没有定义这个方法,但是 dict 是知道有这么个东西存在的。也就是说,如果有一个类继承了 dict,然后这个继承类提供了 __missing__ 方法,那么在 __getitem__ 碰到找不到的键的时候,Python 就会自动调用它,而不是抛出一个 KeyError 异常。

   __missing__ 方法只会被 __getitem__ 调用(比如在表达式 d[k] 中)。提供 __missing__ 方法对 get 或者__contains__(in 运算符会用到这个方法)这些方法的使用没有影响。这也是我在上一节最后的警告中提到,defaultdict 中的default_factory 只对 __getitem__ 有作用的原因。

   举个?  StrKeyDict0 在查询的时候把非字符串的键转换为字符

 1 #StrKeyDict0 继承了 dict
 2 class StrKeyDict0(dict):
 3 
 4     #如果找不到的键本身就是字符串,那就抛出 KeyError 异常。
 5     def __missing__(self, key):
 6         if isinstance(key, str):
 7             raise KeyError(key)
 8         #如果找不到的键不是字符串,那么把它转换成字符串再进行查找(递归查询),会调用__getitem__
 9         return self[str(key)]
10 
11     #get 方法把查找工作用 self[key] 的形式委托给 __getitem__
12     def get(self, key, default=None):
13         try:
14             return self[key]
15         except KeyError:
16             return default
17 
18     def __contains__(self, key):
19         return key in self.keys() or str(key) in self.keys()
20 
21 d = StrKeyDict0([('2', 'two'), ('4', 'four')])
22 print(d['2'])
23 print(d['4'])

 

字典的变种

collections.OrderedDict

  这个类型在添加键的时候会保持顺序,因此键的迭代次序总是一致的。OrderedDict 的 popitem 方法默认删除并返回的是字典里的最后一个元素,但是如果像 my_odict.popitem(last=False) 这样调用它,那么它删除并返回第一个被添加进去的元素。

collections.Counter

  这个映射类型会给键准备一个整数计数器。每次更新一个键的时候都会增加这个计数器。所以这个类型可以用来给可散列表对象计数,或者是当成多重集来用——多重集合就是集合里的元素可以出现不止一次。Counter 实现了 + 和 - 运算符用来合并记录,还有像most_common([n]) 这类很有用的方法。most_common([n]) 会按照次序返回映射里最常见的 n 个键和它们的计数。

举个?

 1 from collections import Counter
 2 
 3 
 4 #创建一个计数的对象
 5 ct = Counter('abracadabra')
 6 print(ct)
 7 
 8 #更新
 9 ct.update('aaageagaege')
10 
11 print(ct)
12 
13 #打印最多的两个
14 print(ct.most_common(2))

以上代码执行的结果为:

Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
Counter({'a': 10, 'g': 3, 'e': 3, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
[('a', 10), ('g', 3)]

 

子类化UserDict

  就创造自定义映射类型来说,以 UserDict 为基类,总比以普通的dict 为基类要来得方便。

  无论是添加、更新还是查询操作,StrKeyDict 都会把非字符串的键转换为字符串。

 1 import collections
 2 
 3 
 4 class StrKeyDict(collections.UserDict):
 5 
 6     def __missing__(self, key):
 7         if isinstance(key, str):
 8             raise KeyError(key)
 9         return self[str(key)]
10 
11     def __contains__(self, key):
12         return key in self.keys() or str(key) in self.keys()
13 
14     #__setitem__ 会把所有的键都转换成字符串
15     def __setitem__(self, key, item):
16         self.data[str(key)] = item

  因为 UserDict 继承的是 MutableMapping,所以 StrKeyDict 里剩下的那些映射类型的方法都是从 UserDict、MutableMapping 和Mapping 这些超类继承而来的。特别是最后的 Mapping 类,它虽然是一个抽象基类(ABC),但它却提供了好几个实用的方法。以下两个方法值得关注。

MutableMapping.update

  这个方法不但可以为我们所直接利用,它还用在 __init__ 里,让构造方法可以利用传入的各种参数(其他映射类型、元素是 (key,value) 对的可迭代对象和键值参数)来新建实例。因为这个方法在背后是用 self[key] = value 来添加新值的,所以它其实是在使用我们的 __setitem__ 方法。  

Mapping.get

 在 StrKeyDict0中,我们不得不改写 get 方法,好让它的表现跟 __getitem__ 一致。

 

不可变映射类型

  从 Python 3.3 开始,types 模块中引入了一个封装类名叫MappingProxyType。如果给这个类一个映射,它会返回一个只读的映射视图。虽然是个只读视图,但是它是动态的。这意味着如果对原映射做出了改动,我们通过这个视图可以观察到,但是无法通过这个视图对原映射做出修改。

举个? 用 MappingProxyType 来获取字典的只读实例

>>> from types import MappingProxyType
>>> d = {'1': 'A'}
>>> d_proxy = MappingProxyType(d)
>>> d_proxy
mappingproxy({'1': 'A'})
>>> d_proxy['2'] = 'b'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'mappingproxy' object does not support item assignment
>>> d['2'] = 'b'
>>> d_proxy['2']
'b'

 

集合论

  “集”这个概念在 Python 中算是比较年轻的,同时它的使用率也比较低。set 和它的不可变的姊妹类型 frozenset 直到 Python 2.3 才首次以模块的形式出现,然后在 Python 2.6 中它们升级成为内置类型。

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

>>> l = ['spam', 'spam', 'eggs', 'spam']
>>> set(l)
{'eggs', 'spam'}
>>> list(set(l))
['eggs', 'spam']

集合字面量

  除空集之外,集合的字面量——{1}、{1, 2},等等——看起来跟它的数学形式一模一样。如果是空集,那么必须写成 set() 的形式。

  在 python 3 里面,除了空集,集合的字符串表示形式总是以 {...} 的形式出现。

?

>>> s = {1}
>>> type(s)
<class 'set'>
>>> s
{1}
>>> s.pop()
1
>>> s
set()

  由于 Python 里没有针对 frozenset 的特殊字面量句法,我们只能采用构造方法。Python 3 里 frozenset 的标准字符串表示形式看起来就像构造方法调用一样。来看这段控制台对话:

>>> frozenset(range(10))
frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9})

集合推导

举个?  新建一个 Latin-1 字符集合,该集合里的每个字符的Unicode 名字里都有“SIGN”这个单词

>>> from unicodedata import name
>>> {chr(i) for i in range(32, 256) if 'SIGN' in name(chr(i),'')}
{'°', '#', '', 'µ', '£', '%', '§', '¤', '©', '<', '+', '±', '®', '¬', '¥', '÷', '¢', '>', '×', '$', '='}

集合的操作

>>> a = set((1,2,3,4,5))
>>> a
{1, 2, 3, 4, 5}
>>> b = set((4,5,6,7,8,10))
>>> a
{1, 2, 3, 4, 5}
>>> b
{4, 5, 6, 7, 8, 10}
>>> a - b
{1, 2, 3}
>>> b - a
{8, 10, 6, 7}
>>> a | b
{1, 2, 3, 4, 5, 6, 7, 8, 10}
>>> a & b
{4, 5}
>>> a ^ b
{1, 2, 3, 6, 7, 8, 10}

常用的操作:

  1. 交集:a & b
  2. 并集:a  |  b
  3. 差集:a  - b
  4. 对称差集:a ^ b 

 

转载于:https://www.cnblogs.com/demon89/p/7376008.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值