Python的集合(collections)模块,为很多用其他方法很难实现的场景提供了解决方案。本文我们将会学习该模块的抽象概念是如何产生的,日后处理不同问题的过程中迟早会用得到这些知识。免责声明:这篇文章是关于Python的一个相当高级的特性。如果你刚入门,建议先收藏,请等一等再学!
一、模块概述
1、模块作用
官方说法:collections模块实现了特定目标的容器,以提供Python标准内建容器dict ,list , set , 和tuple的替代选择。
通俗说法:Python内置的数据类型和方法,collections模块在这些内置类型的基础提供了额外的高性能数据类型,比如基础的字典是不支持顺序的,collections模块的OrderedDict类构建的字典可以支持顺序,collections模块的这些扩展的类用处非常大,熟练掌握该模块,可以大大简化Python代码,提高Python代码逼格和效率,高手入门必备。
2、模块资料
关于该模块,官方的参考资料写的非常详细,也很有价值,大家可以参考
中文文档:https://docs.python.org/zh-cn/3/library/collections.html
英文文档:https://docs.python.org/3/library/collections.html#module-collections
3、模块子类
用collections.__all__查看所有的子类,一共包含9个
import collections
print(collections.__all__)
['deque', 'defaultdict', 'namedtuple', 'UserDict', 'UserList', 'UserString', 'Counter', 'OrderedDict','ChainMap']
二、计数器-Counter
1、基础介绍
一个计数器工具提供快速和方便的计数,Counter是一个dict的子类,用于计数可哈希对象。它是一个集合,元素像字典键(key)一样存储,它们的计数存储为值。计数可以是任何整数值,包括0和负数,Counter类有点像其他语言中的bags或multisets。简单说,就是可以统计计数,来几个例子看看就清楚了,比如
#计算top10的单词
from collections import Counter
import re
text = 'remove an existing key one level down remove an existing key one level down'
words = re.findall(r'\w+', text)
Counter(words).most_common(10)
[('remove', 2),('an', 2),('existing', 2),('key', 2),('one', 2)('level', 2),('down', 2)]
#计算列表中单词的个数
cnt = Counter()
for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']:
cnt[word] += 1
cnt
Counter({'red': 2, 'blue': 3, 'green': 1})
#上述这样计算有点嘛,下面的方法更简单,直接计算就行
L = ['red', 'blue', 'red', 'green', 'blue', 'blue']
Counter(L)
Counter({'red': 2, 'blue': 3, 'green': 1}
元素从一个iterable 被计数或从其他的mapping (or counter)初始化:
from collections import Counter
#字符串计数
Counter('gallahad')
Counter({'g': 1, 'a': 3, 'l': 2, 'h': 1, 'd': 1})
#字典计数
Counter({'red': 4, 'blue': 2})
Counter({'red': 4, 'blue': 2})
#是个啥玩意计数
Counter(cats=4, dogs=8)
Counter({'cats': 4, 'dogs': 8})
Counter(['red', 'blue', 'red', 'green', 'blue', 'blue'])
Counter({'red': 2, 'blue': 3, 'green': 1
计数器对象除了字典方法以外,还提供了三个其他的方法:
1、elements()
描述:返回一个迭代器,其中每个元素将重复出现计数值所指定次。 元素会按首次出现的顺序返回。 如果一个元素的计数值小于1,elements() 将会忽略它。
语法:elements( )参数:无c = Counter(a=4, b=2, c=0, d=-2)
list(c.elements())
['a', 'a', 'a', 'a', 'b', 'b']
sorted(c.elements())
['a', 'a', 'a', 'a', 'b', 'b']
c = Counter(a=4, b=2, c=0, d=5)
list(c.elements())
['a', 'a', 'a', 'a', 'b', 'b', 'd', 'd', 'd', 'd', 'd'
2、most_common()
返回一个列表,其中包含n个最常见的元素及出现次数,按常见程度由高到低排序。 如果 n 被省略或为None,most_common() 将返回计数器中的所有元素,计数值相等的元素按首次出现的顺序排序,经常用来计算top词频的词语。
Counter('abracadabra').most_common(3)
[('a', 5), ('b', 2), ('r', 2)]
Counter('abracadabra').most_common(5)
[('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)]
3、subtract()
从迭代对象或映射对象减去元素。像dict.update() 但是是减去,而不是替换。输入和输出都可以是0或者负数。
c = Counter(a=4, b=2, c=0, d=-2)
d = Counter(a=1, b=2, c=3, d=4)
c.subtract(d)
c
Counter({'a': 3, 'b': 0, 'c': -3, 'd': -6})
#减去一个abcd
str0 = Counter('aabbccdde')
str0
Counter({'a': 2, 'b': 2, 'c': 2, 'd': 2, 'e': 1})
str0.subtract('abcd')
str0
Counter({'a': 1, 'b': 1, 'c': 1, 'd': 1, 'e': 1}
4、字典方法
通常字典方法都可用于Counter对象,除了有两个方法工作方式与字典并不相同。
fromkeys(iterable)
这个类方法没有在Counter中实现。
update([iterable-or-mapping])
从迭代对象计数元素或者从另一个映射对象 (或计数器) 添加。 像 dict.update() 但是是加上,而不是替换。另外,迭代对象应该是序列元素,而不是一个 (key, value) 对。
sum(c.values()) # total of all counts
c.clear() # reset all counts
list(c) # list unique elements
set(c) # convert to a set
dict(c) # convert to a regular dictionary
c.items() # convert to a list of (elem, cnt) pairs
Counter(dict(list_of_pairs)) # convert from a list of (elem, cnt) pairs
c.most_common()[:-n-1:-1] # n least common elements
+c # remove zero and negative counts
5、数学操作
这个功能非常强大,提供了几个数学操作,可以结合 Counter 对象,以生产 multisets (计数器中大于0的元素)。 加和减,结合计数器,通过加上或者减去元素的相应计数。交集和并集返回相应计数的最小或最大值。每种操作都可以接受带符号的计数,但是输出会忽略掉结果为零或者小于零的计数。
c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
c + d # add two counters together: c[x] + d[x]
Counter({'a': 4, 'b': 3})
c - d # subtract (keeping only positive counts)
Counter({'a': 2})
c & d # intersection: min(c[x], d[x])
Counter({'a': 1, 'b': 1})
c | d # union: max(c[x], d[x])
Counter({'a': 3, 'b': 2})
单目加和减(一元操作符)意思是从空计数器加或者减去。
c = Counter(a=2, b=-4)
+c
Counter({'a': 2})
-c
Counter({'b': 4})
写一个计算文本相似的算法,加权相似
def str_sim(str_0,str_1,topn):
topn = int(topn)
collect0 = Counter(dict(Counter(str_0).most_common(topn)))
collect1 = Counter(dict(Counter(str_1).most_common(topn)))
jiao = collect0 & collect1
bing = collect0 | collect1
sim = float(sum(jiao.values()))/float(sum(bing.values()))
return(sim)
str_0 = '定位手机定位汽车定位GPS定位人定位位置查询'
str_1 = '导航定位手机定位汽车定位GPS定位人定位位置查询'
str_sim(str_0,str_1,5)
0.75
二、双向队列-deque
双端队列,可以快速的从另外一侧追加和推出对象,deque是一个双向链表,针对list连续的数据结构插入和删除进行优化。它提供了两端都可以操作的序列,这表示在序列的前后你都可以执行添加或删除操作。双向队列(deque)对象支持以下方法:
1、append()
添加 x 到右端。
d = deque('ghi')
d.append('j')
d
deque(['g', 'h', 'i', 'j'])
2、appendleft()
添加 x 到左端。
d.appendleft('f')
d
deque(['f', 'g', 'h', 'i', 'j'])
3、clear()
移除所有元素,使其长度为0.
d = deque('ghi')
d.clear()
d
deque([])
4、copy()
创建一份浅拷贝。
d = deque('xiaoweuge')
y = d.copy()
print(y)
deque(['x', 'i', 'a', 'o', 'w', 'e', 'u', 'g', 'e'])
5、count()
计算 deque 中元素等于 x 的个数。
d = deque('xiaoweuge-shuai')
d.count('a')
2
6、extend()
扩展deque的右侧,通过添加iterable参数中的元素。
a = deque('abc')
b = deque('cd')
a.extend(b)
a
deque(['a', 'b', 'c', 'c', 'd'])
与append 的区别
a = deque('abc')
b = deque('cd')
a.append(b)
deque(['a', 'b', 'c', deque(['c', 'd'])])
7、extendleft()
扩展deque的左侧,通过添加iterable参数中的元素。
注意,左添加时,在结果中iterable参数中的顺序将被反过来添加。
a = deque('abc')
b = deque('cd')
a.extendleft(b)
a
deque(['d', 'c', 'a', 'b', 'c'])
8、index()
返回 x 在 deque 中的位置(在索引 start 之后,索引 stop 之前)。
返回第一个匹配项,如果未找到则引发 ValueError。
3.5 新版功能.
d = deque('xiaoweuge')
d.index('w')
4
9、insert()
在位置 i 插入 x 。
如果插入会导致一个限长 deque 超出长度 maxlen 的话,就引发一个 IndexError。
a = deque('abc')
a.insert(1,'X')
deque(['a', 'X', 'b', 'c'])
10、pop()
移去并且返回一个元素,deque 最右侧的那一个。
如果没有元素的话,就引发一个 IndexError。
d.pop()
'j'
11、popleft()
移去并且返回一个元素,deque 最左侧的那一个。
如果没有元素的话,就引发 IndexError。
d.popleft()
'f'
12、remove(value)
移除找到的第一个 value。
如果没有的话就引发 ValueError。
a = deque('abca')
a.remove('a')
a
deque(['b', 'c', 'a'])
13、reverse()
将deque逆序排列。
返回 None 。
#逆序排列
d = deque('ghi') # 创建一个deque
list(reversed(d))
['i', 'h', 'g']
deque(reversed(d))
deque(['i', 'h', 'g'])
14、rotate(n=1)
向右循环移动 n 步。
如果 n 是负数,就向左循环。如果deque不是空的,向右循环移动一步就等价于
d.appendleft(d.pop()) , 向左循环一步就等价于 d.append(d.popleft()) 。
# 向右边挤一挤
d = deque('ghijkl')
d.rotate(1)
d
deque(['l', 'g', 'h', 'i', 'j', 'k'])
# 向左边挤一挤
d.rotate(-1)
d
deque(['g', 'h', 'i', 'j', 'k', 'l'])
#看一个更明显的
x = deque('12345')
x
deque(['1', '2', '3', '4', '5'])
x.rotate()
x
deque(['5', '1', '2', '3', '4'])
d = deque(['12','av','cd'])
d.rotate(1)
deque(['cd', '12', 'av'
15、maxlen
Deque的最大尺寸,如果没有限定的话就是 None 。
from collections import deque
d=deque(maxlen=10)
for i in range(20):
d.append(i)
d
deque([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
除了以上操作,deque还支持迭代、封存、len(d)、reversed(d)、copy.deepcopy(d)、copy.copy(d)、成员检测运算符 in 以及下标引用例如通过 d[0] 访问首个元素等。 索引访问在两端的复杂度均为 O(1) 但在中间则会低至 O(n)。 如需快速随机访问,请改用列表。
Deque从版本3.5开始支持 add(), mul(), 和 imul() 。
from collections import deque
d = deque('ghi') # 创建一个deque
for elem in d:
print(elem.upper())
G
H
I
#从右边添加一个元素
d.append('j')
d
deque(['g', 'h', 'i', 'j'])
#从左边添加一个元素
d.appendleft('f')
d
deque(['f', 'g', 'h', 'i', 'j'])
#右边删除
d.pop()
'j'
#左边边删除
d.popleft()
'f'
#看看还剩下啥
list(d) #
['g', 'h', 'i']
#成员检测
'h' in d
True
#添加多个元素
d.extend('jkl')
d
deque(['g', 'h', 'i', 'j', 'k', 'l'])
d.clear() # empty the deque
d.pop() # cannot pop from an empty deque
Traceback (most recent call last):
File "<pyshell#6>", line 1, in -toplevel-
d.pop()
IndexError: pop from an empty deque
d.extendleft('abc') # extendleft() reverses the input order
d
deque(['c', 'b',
三、有序字典-OrderedDict
有序词典就像常规词典一样,但有一些与排序操作相关的额外功能,popitem() 方法有不同的签名。它接受一个可选参数来指定弹出哪个元素。move_to_end() 方法,可以有效地将元素移动到任一端。
有序词典就像常规词典一样,但有一些与排序操作相关的额外功能。由于内置的 dict 类获得了记住插入顺序的能力(在 Python 3.7 中保证了这种新行为),它们变得不那么重要了。
一些与 dict 的不同仍然存在:
- 常规的 dict 被设计为非常擅长映射操作。 跟踪插入顺序是次要的。 OrderedDict 旨在擅长重新排序操作。
- 空间效率、迭代速度和更新操作的性能是次要的。算法上, OrderedDict 可以比 dict 更好地处理频繁的重新排序操作。
- 这使其适用于跟踪最近的访问(例如在 LRU cache 中)。
- 对于 OrderedDict ,相等操作检查匹配顺序。
- OrderedDict 类的 popitem() 方法有不同的签名。 它接受一个可选参数来指定弹出哪个元素。 OrderedDict
- 类有一个 move_to_end() 方法,可以有效地将元素移动到任一端。
- Python 3.8之前, dict 缺少 reversed() 方法。
1、popitem
语法:popitem(last=True)
功能:有序字典的 popitem() 方法移除并返回一个 (key, value) 键值对。 如果 last 值为真,则按 LIFO 后进先出的顺序返回键值对,否则就按 FIFO 先进先出的顺序返回键值对。
from collections import OrderedDict
d = OrderedDict.fromkeys('abcde')
d.popitem()
('e', None)
d
OrderedDict([('a', None), ('b', None), ('c', None), ('d', None)])
#last=False时,弹出第一个
d = OrderedDict.fromkeys('abcde')
''.join(d.keys())
'abcde'
d.popitem(last=False)
''.join(d.keys())
'bcde'
2、move_to_end
from collections import OrderedDict
d = OrderedDict.fromkeys('abcde')
d.move_to_end('b')
''.join(d.keys())
'acdeb'
d
OrderedDict([('a', None), ('c', None), ('d', None), ('e', None), ('b', None)])
d.move_to_end('b', last=False)
''.join(d.keys())
'bacde'
3、reversed()
相对于通常的映射方法,有序字典还另外提供了逆序迭代的支持,通过reversed() 。
d = OrderedDict.fromkeys('abcde')
list(reversed(d))
['e', 'd', 'c', 'b', 'a']
四、可命名元组-namedtuple
生成可以使用名字来访问元素内容的tuple子类,命名元组赋予每个位置一个含义,提供可读性和自文档性。它们可以用于任何普通元组,并添加了通过名字获取值的能力,通过索引值也是可以的。
1、参数介绍
namedtuple(typename,field_names,*,verbose=False, rename=False, module=None)
1)typename:该参数指定所创建的tuple子类的类名,相当于用户定义了一个新类。
2)field_names:该参数是一个字符串序列,如 [‘x’,‘y’]。此外,field_names 也可直接使用单个字符串代表所有字段名,多个字段名用空格、逗号隔开,如 ‘x y’ 或 ‘x,y’。任何有效的 Python 标识符都可作为字段名(不能以下画线开头)。有效的标识符可由字母、数字、下画线组成,但不能以数字、下面线开头,也不能是关键字(如 return、global、pass、raise 等)。
3)rename:如果将该参数设为 True,那么无效的字段名将会被自动替换为位置名。例如指定 [‘abc’,‘def’,‘ghi’,‘abc’],它将会被替换为 [‘abc’, ‘_1’,‘ghi’,’_3’],这是因为 def 字段名是关键字,而 abc 字段名重复了。
4)verbose:如果该参数被设为 True,那么当该子类被创建后,该类定义就被立即打印出来。
5)module:如果设置了该参数,那么该类将位于该模块下,因此该自定义类的 module 属性将被设为该参数值。
2、应用案例
1)水族箱案例
Python元组是一个不可变的,或不可改变的,有序的元素序列。元组经常用来表示纵列数据;例如,一个CSV文件中的行数或一个SQL数据库中的行数。一个水族箱可以用一系列元组来记录它的鱼类的库存。
一个单独的鱼类元组:
("Sammy","shark","tank-k")
这个元组由三个字符串元素组成。
虽然在某些方面很有用,但是这个元组并没有清楚地指明它的每个字段代表什么。实际上,元素0是一个名称,元素1是一个物种,元素2是一个饲养箱。
鱼类元组字段说明:
|name|species|tank
|sammy|shark|tank
这个表清楚地表明,该元组的三个元素都有明确的含义。
来自collections模块的namedtuple允许你向一个元组的每个元素添加显式名称,以便在你的Python程序中明确这些元素的含义。
让我们使用namedtuple来生成一个类,从而明确地命名鱼类元组的每个元素:
from collections import namedtuple
可以让你的Python程序访问namedtuple工厂函数。namedtuple()函数调用会返回一个绑定到名称Fish的类。namedtuple()函数有两个参数:我们的新类“Fish”的期望名称和命名元素[“name”、“species”、“tank”]的一个列表。
我们可以使用Fish类来表示前面的鱼类元组:
如果我们运行这段代码,我们将看到以下输出:
sammy是使用Fish类进行实例化的。sammy是一个具有三个明确命名元素的元组。
sammy的字段可以通过它们的名称或者一个传统的元组索引来访问:
如果我们运行这两个print调用,我们将看到以下输出:
访问.species会返回与使用[1]访问sammy的第二个元素相同的值。
使用collections模块中的namedtuple可以在维护元组(即它们是不可变的、有序的)的重要属性的同时使你的程序更具可读性。
此外,namedtuple工厂函数还会向Fish实例添加几个额外的方法。使用._asdict()将一个实例转换为字典:
如果我们运行print,你会看到如下输出:
在sammy上调用.asdict()将返回一个字典,该字典会将三个字段名称分别映射到它们对应的值。
大于3.8的Python版本输出这一行的方式可能略有不同。例如,你可能会看到一个OrderedDict,而不是这里显示的普通字典。
2)加法器案例
from collections import namedtuple
#定义命名元组类:Point
Point = namedtuple('Point', ['x', 'y'])
#初始化Point对象,即可用位置参数,也可用命名参数
p = Point(11, y=22)
#像普通元组一样用根据索引访问元素
print(p[0] + p[1])
33
#执行元组解包,按元素的位置解包
a, b = p
print(a, b)
11, 22
#根据字段名访问各元素
print(p.x + p.y)
33
print(p)
Point(x=11, y=22
3、三个方法
备注: 在Python中,带有前导下划线的方法通常被认为是“私有的”。但是,namedtuple提供的其他方法(如._asdict()、._make()、._replace()等)是公开的。
除了继承元组的方法,命名元组还支持三个额外的方法和两个属性。为了防止字段名冲突,方法和属性以下划线开始。
_make(iterable)
类方法从存在的序列或迭代实例创建一个新实例。
t = [14, 55]
Point._make(t)
_asdict()
返回一个新的 dict ,它将字段名称映射到它们对应的值:
p = Point(x=11, y=22)
p._asdict()
OrderedDict([('x', 11), ('y', 22)])
_replace(**kwargs)
返回一个新的命名元组实例,并将指定域替换为新的值
p = Point(x=11, y=22)
p._replace(x=33)
Point(x=33, y=22)
4、两个属性
_fields
字符串元组列出了字段名。用于提醒和从现有元组创建一个新的命名元组类型。
p._fields # view the field names
('x', 'y')
Color = namedtuple('Color', 'red green blue')
Pixel = namedtuple('Pixel', Point._fields + Color._fields)
Pixel(11, 22, 128, 255, 0)
Pixel(x=11, y=22, red=128, green=255, blue=0)
_field_defaults
字典将字段名称映射到默认值。
Account = namedtuple('Account', ['type', 'balance'], defaults=[0])
Account._field_defaults
{'balance': 0}
Account('premium')
Account(type='premium', balance=0)
5、其他函数
getattr()
要获取这个名字域的值,使用 getattr() 函数 :
getattr(p, 'x')
11
转换一个字典到命名元组,使用 ** 两星操作符
d = {'x': 11, 'y': 22}
Point(**d)
Point(x=11, y=22)
因为一个命名元组是一个正常的Python类,它可以很容易的通过子类更改功能。这里是如何添加一个计算域和定宽输出打印格式:
class Point(namedtuple('Point', ['x', 'y'])):
__slots__ = ()
@property
def hypot(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
def __str__(self):
return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot)
for p in Point(3, 4), Point(14, 5/7):
print(p)
Point: x= 3.000 y= 4.000 hypot= 5.000
Point: x=14.000 y= 0.714 hypot=14.018
五、默认字典-defaultdict
在Python字典中收集数据通常是很有用的。
在字典中获取一个 key 有两种方法, 第一种 get , 第二种 通过 [] 获取.
使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict。
当我使用普通的字典时,用法一般是dict={},添加元素的只需要dict[element] =value即,调用的时候也是如此,dict[element] = xxx,但前提是element字典里,如果不在字典里就会报错
这时defaultdict就能排上用场了,defaultdict的作用是在于,当字典里的key不存在但被查找时,返回的不是keyError而是一个默认值,这个默认值是什么呢,下面会说
1、基础介绍
defaultdict([default_factory[, …]])
返回一个新的类似字典的对象。 defaultdict是内置dict类的子类。它重载了一个方法并添加了一个可写的实例变量。其余的功能与dict类相同,此处不再重复说明。
本对象包含一个名为default_factory的属性,构造时,第一个参数用于为该属性提供初始值,默认为 None。所有其他参数(包括关键字参数)都相当于传递给 dict 的构造函数。
defaultdict 对象除了支持标准 dict 的操作,还支持以下方法作为扩展:
missing(key)
如果 default_factory 属性为 None,则调用本方法会抛出 KeyError 异常,附带参数 key。
如果 default_factory 不为 None,则它会被(不带参数地)调用来为 key 提供一个默认值,这个值和 key 作为一对键值对被插入到字典中,并作为本方法的返回值返回。
如果调用 default_factory 时抛出了异常,这个异常会原封不动地向外层传递。
在无法找到所需键值时,本方法会被 dict 中的 getitem() 方法调用。无论本方法返回了值还是抛出了异常,都会被 getitem() 传递。
注意,missing() 不会 被 getitem() 以外的其他方法调用。意味着 get() 会像正常的 dict 那样返回 None,而不是使用 default_factory。
2、示例介绍
使用 list 作为 default_factory,很轻松地将(键-值对组成的)序列转换为(键-列表组成的)字典
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d = defaultdict(list)
for k, v in s:
d[k].append(v)
sorted(d.items())
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]
当每个键第一次遇见时,它还没有在字典里面,所以自动创建该条目,即调用default_factory方法,返回一个空的 list。 list.append() 操作添加值到这个新的列表里。当再次存取该键时,就正常操作,list.append() 添加另一个值到列表中。这个计数比它的等价方法dict.setdefault()要快速和简单:
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d = {}
for k, v in s:
d.setdefault(k, []).append(v)
sorted(d.items())
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]
设置 default_factory为int,使defaultdict用于计数(类似其他语言中的 bag或multiset):
s = 'mississippi'
d = defaultdict(int)
for k in s:
d[k] += 1
sorted(d.items())
[('i', 4), ('m', 1), ('p', 2), ('s', 4)]
设置 default_factory 为 set 使 defaultdict 用于构建 set 集合:
s = [('red', 1), ('blue', 2), ('red', 3), ('blue', 4), ('red', 1), ('blue', 4)]
d = defaultdict(set)
for k, v in s:
d[k].add(v)
sorted(d.items())
[('blue', {2, 4}), ('red', {1, 3})]
defaultdict绝不会引发一个KeyError。如果一个键不存在,defaultdict会插入并返回一个占位符值来代替:
如果我们运行这段代码,我们将看到如下输出:
defaultdict会插入并返回一个占位符值,而不是引发一个KeyError。在本例中,我们将占位符值指定为一个列表。
相比之下,常规字典会在缺失的键上引发一个KeyError:
如果我们运行这段代码,我们将看到如下输出:
当我们试图访问一个不存在的键时,常规字典my_regular_dict会引发一个KeyError。
defaultdict的行为与常规字典不同。defaultdict会不带任何参数调用占位符值来创建一个新对象,而不是在缺失的键上引发一个KeyError。在本例中,是调用list()创建一个空列表。
继续我们虚构的水族箱示例,假设我们有一个表示水族箱清单的鱼类元组列表:
水族箱中有三种鱼——它们的名字、种类和饲养箱在这三个元组中都有指出。
我们的目标是按饲养箱组织我们的清单—我们想知道每个饲养箱中存在的鱼的列表。换句话说,我们需要一个能将“tank-a”映射到[“Jamie”, “Mary”] ,并且将“tank-b”映射到[“Jamie”]的字典。
我们可以使用defaultdict来按饲养箱对鱼进行分组:
运行这段代码,我们将看到以下输出:
fish_names_by_tank被声明为一个defaultdict,它默认会插入list()而不是引发一个KeyError。由于这保证了fish_names_by_tank中的每个键都将指向一个list,所以我们可以自由地调用.append()来将名称添加到每个饲养箱的列表中。
这里,defaultdict帮助你减少了出现未预期的KeyErrors的机会。减少未预期的KeyErrors意味着你可以用更少的行更清晰地编写你的程序。更具体地说,defaultdict习惯用法让你避免了手动地为每个饲养箱实例化一个空列表。
如果没有 defaultdict, for循环体可能看起来更像这样:
使用常规字典(而不是defaultdict)意味着for循环体总是必须检查fish_names_by_tank中给定的tank是否存在。只有在验证了fish_names_by_tank中已经存在tank,或者已经使用一个[]初始化了tank之后,我们才可以添加鱼类名称。
在填充字典时,defaultdict可以帮助我们减少样板代码,因为它从不引发KeyError。
六、映射链-ChainMap
1、ChainMap是什么
ChainMap最基本的使用,可以用来合并两个或者更多个字典,当查询的时候,从前往后依次查询。
ChainMap:将多个字典视为一个,解锁Python超能力。
ChainMap是由Python标准库提供的一种数据结构,允许你将多个字典视为一个。换句话说:ChainMap是一个基于多dict的可更新的视图,它的行为就像一个普通的dict。
ChainMap类用于快速链接多个映射,以便将它们视为一个单元。它通常比创建新字典和多次调用update()快得多。
你以前可能从来没有听说过ChainMap,你可能会认为ChainMap的使用情况是非常特定的。坦率地说,你是对的。
我知道的用例包括:
- 通过多个字典搜索
- 提供链缺省值
- 经常计算字典子集的性能关键的应用程序
2、特性
1)找到一个就不找了:这个列表是按照第一次搜索到最后一次搜索的顺序组织的,搜索查询底层映射,直到一个键被找到。
2)更新原始映射:不同的是,写,更新和删除只操作第一个映射。
3)支持所有常用字典方法。
简而言之ChainMap:将多个字典视为一个,解锁Python超能力。
Python标准库中的集合模块包含许多为性能而设计的实用的数据结构。著名的包括命名元组或计数器。
今天,通过实例,我们来看看鲜为人知的ChainMap。通过浏览具体的示例,我希望给你一个提示,关于在更高级的Python工作中使用ChainMap将如何从中受益。
3、应用案例-基础案例
from collections import ChainMap
baseline = {'music': 'bach', 'art': 'rembrandt'}
adjustments = {'art': 'van gogh', 'opera': 'carmen'}
ChainMap(adjustments, baseline)
ChainMap({'art': 'van gogh', 'opera': 'carmen'}, {'music': 'bach', 'art': 'rembrandt'})
list(ChainMap(adjustments, baseline))
['music', 'art', 'opera']
#存在重复元素时,也不会去重
dcic1 = {'label1': '11', 'label2': '22'}
dcic2 = {'label2': '22', 'label3': '33'}
dcic3 = {'label4': '44', 'label5': '55'}
last = ChainMap(dcic1, dcic2,dcic3)
last
ChainMap({'label1': '11', 'label2': '22'}, {'label2': '22', 'label3': '33'}, {'label4': '44', 'label5': '55'})
new_child()方法
用法:new_child(m=None)
返回一个新的ChainMap类,包含了一个新映射(map),后面跟随当前实例的全部映射map。如果m被指定,它就成为不同新的实例,就是在所有映射前加上 m,如果没有指定,就加上一个空字典,这样的话一个 d.new_child() 调用等价于ChainMap({}, *d.maps) 。这个方法用于创建子上下文,不改变任何父映射的值。
last.new_child(m={'key_new':888})
ChainMap({'key_new': 888}, {'label1': '11', 'label2': '22'}, {'label2': '22', 'label3': '33'}, {'label4': '44', 'label5': '55'})
parents属性
属性返回一个新的ChainMap包含所有的当前实例的映射,除了第一个。这样可以在搜索的时候跳过第一个映射。使用的场景类似在 nested scopes 嵌套作用域中使用nonlocal关键词。用例也可以类比内建函数super() 。一个d.parents 的引用等价于ChainMap(*d.maps[1:]) 。
last.parents
ChainMap({'label2': '22', 'label3': '33'}, {'label4': '44', 'label5': '55'})
4、应用案例-购物清单
作为使用ChainMap的第一个例子,让我们考虑一张购物清单。我们的清单可能包含玩具,电脑,甚至衣服。所有这些条目都有价格,所以我们将把我们的条目存储在名称价格映射中。
toys = {'Blocks':30,'Monopoly':20}
computers = {'iMac':1000,'Chromebook':1000,'PC':400}
clothing = {'Jeans':40,'T-shirt':10}
现在我们可以使用ChainMap在这些不同的集合上建立一个单一的视图:
from collections import ChainMap
inventory = ChainMap(toys,computers,clothing)
这使得我们可以查询清单,就像它是一个单一的字典:
inventory['Monopoly']
20
inventory['Jeans']40
正如官方文档所述,ChainMap支持所有常用的字典方法。我们可以使用.get()来搜索可能不存在的条目,或者使用 .pop()删除条目。
inventory.get('Blocks-1')
None
inventory.get('Chromebook')
1000
inventory.pop('Blocks')
inventory
ChainMap({'Monopoly': 20}, {'iMac': 1000, 'Chromebook': 1000, 'PC': 400}, {'Jeans': 40, 'T-shirt': 10})
如果我们现在把玩具添加到toys字典里,它也将在清单中可用。这是ChainMap的可更新的方面。
toys['Nintendo'] = 20
inventory['Nintendo']
20
Oh和ChainMap有一个恰当的字符串表示形式:
str(inventory)
"ChainMap({'Monopoly': 20, 'Nintendo': 20}, {'iMac': 1000, 'Chromebook': 1000, 'PC': 400}, {'Jeans': 40, 'T-shirt': 10})"
一个很好的特点是,在我们的例子中,toys, computers和clothing都是在相同的上下文中(解释器),它们可以来自完全不同的模块或包。这是因为ChainMap通过引用存储底层字典。
第一个例子是使用ChainMap一次搜索多个字典。
事实上,当构建ChainMap时,我们所做的就是有效地构建一系列字典。当查找清单中的一个项时,toys首先被查找,然后是computers,最后是clothing。
ChainMap真的只是一个映射链!
实际上,ChainMap的另一个任务是维护链的默认值。
我们将以一个命令行应用程序的例子来说明这是什么意思。
5、应用案例-CLI配置
让我们面对现实,管理命令行应用程序的配置可能是困难的。配置来自多个源:命令行参数、环境变量、本地文件等。
我们通常实施优先级的概念:如果A和B都定义参数P,A的P值将被使用,因为它的优先级高于B。
例如,如果传递了命令行参数,我们可能希望在环境变量上使用命令行参数。如何轻松地管理配置源的优先级?
一个答案是将所有配置源存储在ChainMap中。
因为ChainMap中的查找是按顺序连续地对每个底层映射执行的(按照他们传给构造函数的顺序),所以我们可以很容易地实现我们寻找的优先级。
下面是一个简单的命令行应用程序。调试参数从命令行参数、环境变量或硬编码默认值中提取:
在执行脚本时,我们可以检查是否首先在命令行参数中查找debug,然后是环境变量,最后是默认值:
这样看上去就非常整洁,对吧?
6、我为什么关心?
坦率地说,ChainMap是那些你可以忽略的Python特性之一。
还有其他ChainMap的替代方案。例如,使用更新循环—例如创建一个dict并用字典update()它—可能奏效。但是,这只有在您不需要跟踪项目的起源时才有效,就像我们的多源CLI配置示例中的情况一样。但是,当你知道ChainMap存在的时候,ChainMap可以让你更轻松,你的代码更优雅。
7、总结
总而言之,我们一起看了ChainMap是什么,一些具体的使用示例,以及如何在现实生活中,性能关键的应用程序中使用ChainMap。如果您想了解更多关于Python的高性能数据容器的信息,请务必从Python的标准库中collections模块中查看其他出色类和函数。
七、UserDict
UserDict类是用作字典对象的外包装。对这个类的需求已部分由直接创建dict的子类的功能所替代;不过这个类处理起来更容易,因为底层的字典可以作为属性来访问。
模拟一个字典类。这个实例的内容保存为一个正常字典,可以通过UserDict实例的data属性存取。如果提供了initialdata 值, data 就被初始化为它的内容,注意一个 initialdata 的引用不会被保留作为其他用途。
UserDict 实例提供了以下属性作为扩展方法和操作的支持:data一个真实的字典,用于保存 UserDict 类的内容。
八、UserList
这个类封装了列表对象。它是一个有用的基础类,对于你想自定义的类似列表的类,可以继承和覆盖现有的方法,也可以添加新的方法。这样我们可以对列表添加新的行为。
对这个类的需求已部分由直接创建 list 的子类的功能所替代;不过,这个类处理起来更容易,因为底层的列表可以作为属性来访问。
模拟一个列表。这个实例的内容被保存为一个正常列表,通过 UserList 的 data 属性存取。实例内容被初始化为一个 list 的copy,默认为 [] 空列表。 list可以是迭代对象,比如一个Python列表,或者一个UserList 对象。
UserList 提供了以下属性作为可变序列的方法和操作的扩展:data
一个 list 对象用于存储 UserList 的内容。
子类化的要求: UserList 的子类需要提供一个构造器,可以无参数调用,或者一个参数调用。返回一个新序列的列表操作需要创建一个实现类的实例。它假定了构造器可以以一个参数进行调用,这个参数是一个序列对象,作为数据源。
如果一个分离的类不希望依照这个需求,所有的特殊方法就必须重写;请参照源代码进行修改。
九、UserString
UserString类是用作字符串对象的外包装。对这个类的需求已部分由直接创建str的子类的功能所替代,不过这个类处理起来更容易,因为底层的字符串可以作为属性来访问。
模拟一个字符串对象。这个实例对象的内容保存为一个正常字符串,通过UserString的data属性存取。实例内容初始化设置为seq的copy。seq 参数可以是任何可通过内建str()函数转换为字符串的对象。
UserString 提供了以下属性作为字符串方法和操作的额外支持:data一个真正的str对象用来存放 UserString 类的内容。