【万字长文详解】Python库collections,让你击败99%的Pythoner

Python的collections库实现了特定目标的容器,以提供Python标准内建容器 dict , list , set , 和 tuple 的替代选择。

为很多用其他方法很难实现的场景提供了解决方案。本文我们将会学习该模块的抽象概念是如何产生的,日后处理不同问题的过程中迟早会用得到这些知识。

免责声明:这篇文章是关于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#module-collections

英文文档: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']

这个模块实现了特定目标的容器,以提供Python标准内建容器dict , list , set , 和tuple 的替代选择。

namedtuple()

创建命名元组子类的工厂函数,生成可以使用名字来访问元素内容的tuple子类

deque

类似列表(list)的容器,实现了在两端快速添加(append)和弹出(pop)

ChainMap

类似字典(dict)的容器类,将多个映射集合到一个视图里面

Counter

字典的子类,提供了可哈希对象的计数功能

OrderedDict

字典的子类,保存了他们被添加的顺序,有序字典

defaultdict

字典的子类,提供了一个工厂函数,为字典查询提供一个默认值

UserDict

封装了字典对象,简化了字典子类化

UserList

封装了列表对象,简化了列表子类化

UserString

封装了字符串对象,简化了字符串子类化(中文版翻译有误)

 

二、计数器-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                       # interp:  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 "", line 1, in -toplevel-
        d.pop()
IndexError: pop from an empty deque
d.extendleft('abc')              # extendleft() reverses the input order
d
deque(['c', 'b', 'a'])

四、有序字典-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__() 方法。

 

传统字典方法

OrderedDict方法

差异

 clear

 clear


 copy

 copy


 fromkeys

 fromkeys


 get

 get


 items

 items


 keys

 keys


 pop

 pop


 popitem

 popitem

OrderedDict   类的 popitem() 方法有不同的签名。它接受一个可选参数来指定弹出哪个元素。

 setdefault

 setdefault


 update

 update


 values

 values



 move_to_end

可以有效地将元素移动到任一端。

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=Falsemodule=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数据库中的行数。一个水族箱可以用一系列元组来记录它的鱼类的库存。

一个单独的鱼类元组:

这个元组由三个字符串元素组成。

虽然在某些方面很有用,但是这个元组并没有清楚地指明它的每个字段代表什么。实际上,元素0是一个名称,元素1是一个物种,元素2是一个饲养箱。

鱼类元组字段说明:

这个表清楚地表明,该元组的三个元素都有明确的含义。

来自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会插入并返回一个占位符值来代替:

【万字长文详解】Python库collections,让你击败99%的Pythoner_第1张图片

如果我们运行这段代码,我们将看到如下输出:

defaultdict会插入并返回一个占位符值,而不是引发一个KeyError。在本例中,我们将占位符值指定为一个列表。

相比之下,常规字典会在缺失的键上引发一个KeyError:

如果我们运行这段代码,我们将看到如下输出:

当我们试图访问一个不存在的键时,常规字典my_regular_dict会引发一个KeyError。

defaultdict的行为与常规字典不同。defaultdict会不带任何参数调用占位符值来创建一个新对象,而不是在缺失的键上引发一个KeyError。在本例中,是调用list()创建一个空列表。

继续我们虚构的水族箱示例,假设我们有一个表示水族箱清单的鱼类元组列表:

 

【万字长文详解】Python库collections,让你击败99%的Pythoner_第2张图片

水族箱中有三种鱼——它们的名字、种类和饲养箱在这三个元组中都有指出。

我们的目标是按饲养箱组织我们的清单—我们想知道每个饲养箱中存在的鱼的列表。换句话说,我们需要一个能将“tank-a”映射到["Jamie", "Mary"] ,并且将“tank-b”映射到["Jamie"]的字典。

我们可以使用defaultdict来按饲养箱对鱼进行分组:

【万字长文详解】Python库collections,让你击败99%的Pythoner_第3张图片

运行这段代码,我们将看到以下输出:

fish_names_by_tank被声明为一个defaultdict,它默认会插入list()而不是引发一个KeyError。由于这保证了fish_names_by_tank中的每个键都将指向一个list,所以我们可以自由地调用.append()来将名称添加到每个饲养箱的列表中。

这里,defaultdict帮助你减少了出现未预期的KeyErrors的机会。减少未预期的KeyErrors意味着你可以用更少的行更清晰地编写你的程序。更具体地说,defaultdict习惯用法让你避免了手动地为每个饲养箱实例化一个空列表。

如果没有 defaultdict, for循环体可能看起来更像这样:

【万字长文详解】Python库collections,让你击败99%的Pythoner_第4张图片

使用常规字典(而不是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中的查找是按顺序连续地对每个底层映射执行的(按照他们传给构造函数的顺序),所以我们可以很容易地实现我们寻找的优先级。

下面是一个简单的命令行应用程序。调试参数从命令行参数、环境变量或硬编码默认值中提取:

【万字长文详解】Python库collections,让你击败99%的Pythoner_第5张图片

在执行脚本时,我们可以检查是否首先在命令行参数中查找debug,然后是环境变量,最后是默认值:

【万字长文详解】Python库collections,让你击败99%的Pythoner_第6张图片

这样看上去就非常整洁,对吧?

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 类的内容。

 


往期精彩回顾



适合初学者入门人工智能的路线及资料下载机器学习及深度学习笔记等资料打印机器学习在线手册深度学习笔记专辑《统计学习方法》的代码复现专辑
AI基础下载机器学习的数学基础专辑
本站知识星球“黄博的机器学习圈子”(92416895)
本站qq群704220115。
加入微信群请扫码:

你可能感兴趣的:(列表,python,java,mooc,xhtml)