collection模块
Python的collections模块
- collections — Container datatypes
namedtuple(): 生成可以使用名字来访问元素内容的tuple子类
deque: 双端队列,可以快速的从另外一侧追加和推出对象
Counter: 计数器,主要用来计数
OrderedDict: 有序字典
defaultdict: 带有默认值的字典collections.abc 抽象基类ChainMap 类似字典(dict)的容器类,将多个映射集合到一个视图里面 UserDict 封装了字典对象,简化了字典子类化 UserList 封装了列表对象,简化了列表子类化 UserString 封装了列表对象,简化了字符串子类化
tuple的几个特性:
- 不可变,iterable
- 拆包
- tuple不可变不是绝对的
- tuple比list好的地方
4.1 immutable的重要性:性能优化(元素全为immutable的会作为常量在编译时确定)、线程安全、可以作为dict的key,拆包特性
4.2 tuple类似struct,list类似array容器序列
list
、tuple
和collections.deque
这些序列能存放不同类型的数据。扁平序列
str
、bytes
、bytearray
、memoryview
和array.array
,这类序列只能容纳一种类型。容器序列存放的是它们所包含的任意类型的对象的引用,而扁平序列里存放的是值而不是引用。换句话说,扁平序列其实是一段连续的内存空间。由此可见扁平序列其实更加紧凑,但是它里面只能存放诸如字符、字节和数值这种基础类型。
序列类型还能按照能否被修改来分类。
可变序列
list
、bytearray
、array.array
、collections.deque
和memoryview
。不可变序列
tuple
、str
和bytes
。
具名元组namedtuple
概念
命名的元组,意味给元组中的每个位置赋予含义,意味着代码可读性更强,namedtuple可以在任何常规元素使用的地方使用,而且它可以通过名称来获取字段信息而不仅仅是通过位置索引。用以构建只有少数属性但是没有方法的对象,比如数据库条目。
collections.namedtuple
是一个工厂函数,它可以用来构建一个带字段名的元组和一个有名字的用
namedtuple
构建的类的实例所消耗的内存跟元组是一样的,因为字段名都被存在对应的类里面创建一个具名元组需要两个参数,一个是类名,另一个是类的各个字段的名字。
几个最有用的:
_fields
类属性、类方法_make(iterable)
和实例方法_asdict()
。❶
_fields
属性是一个包含这个类所有字段名称的元组。❷ 用
_make()
通过接受一个可迭代对象来生成这个类的一个实例,它的作用跟City(*delhi_data)
是一样的。❸
_asdict()
把具名元组以collections.OrderedDict
的形式返回,我们可以利用它来把元组里的信息友好地呈现出来。
1
2
3
4
5
6
7
8 from collections import namedtuple
User = namedtuple("USER", ["name", "age", "city", "height"])
user_1 = User(name="雷鸣", age=21, city="北京", height="175")
print("user_1", user_1.name, user_1.age, user_1.city, user_1.height)
user_1 雷鸣 21 北京 175
user_1._fields
Out[4]: ('name', 'age', 'city', 'height')
使用
定义
代码
1 | # namedtuple: 生成可以使用名字来访问元素内容的tuple子类 |
输出
1 | user_1 雷鸣 21 北京 175 |
访问
代码
1 | from collections import namedtuple |
输出
1 | OrderedDict([('name', '雷姆'), ('age', 17), ('city', '异世界'), ('height', '172')]) |
字典
介绍
dict
类型不但在各种程序里广泛使用,它也是 Python 语言的基石。跟它有关的内置函数都在
__builtins__.__dict__
模块中。正是因为字典至关重要,Python 对它的实现做了高度优化,而散列表则是字典类型性能出众的根本原因。
collections.abc
模块中有Mapping
和MutableMapping
这两个抽象基类然而,非抽象映射类型一般不会直接继承这些抽象基类,它们会直接对
dict
或是collections.UserDict
进行扩展。标准库里的所有映射类型都是利用
dict
来实现的,因此它们有个共同的限制,即只有可散列的数据类型才能用作这些映射里的键关于可散列类型的定义有这样一段话:
如果一个对象是可散列的,那么在这个对象的生命周期中,它的散列值是不变的,而且这个对象需要实现
__hash__()
方法。另外可散列对象还要有__qe__()
方法,这样才能跟其他键做比较。如果两个可散列对象是相等的,那么它们的散列值一定是一样的一般来讲用户自定义的类型的对象都是可散列的,散列值就是它们的
id()
函数的返回值,所以所有这些对象在比较的时候都是不相等的创建字典的不同方式:
1
2
3
4
5
6
7 dict(one=1, two=2, three=3) a =
'one': 1, 'two': 2, 'three': 3} b = {
dict(zip(['one', 'two', 'three'], [1, 2, 3])) c =
dict([('two', 2), ('one', 1), ('three', 3)]) d =
dict({'three': 3, 'one': 1, 'two': 2}) e =
a == b == c == d == e
True除了这些字面句法和灵活的构造方法之外,字典推导(dict comprehension)也可以用来建造新
dict
字典推导(dictcomp)可以从任何以键值对作为元素的可迭代对象中构建出字典。
映射类型的方法其实很丰富。表 3-1 为我们展示了
dict
、defaultdict
和OrderedDict
的常见方法,后面两个数据类型是dict
的变种,位于collections
模块内。字典的变种:
- collections.OrderedDict: 这个类型在添加键的时候会保持顺序,因此键的迭代次序总是一致的。
OrderedDict
的popitem
方法默认删除并返回的是字典里的最后一个元素,但是如果像my_odict.popitem(last=False)
这样调用它,那么它删除并返回第一个被添加进去的元素。- collections.ChainMap: 该类型可以容纳数个不同的映射对象,然后在进行键查找操作的时候,这些对象会被当作一个整体被逐个查找,直到键被找到为止。
- collections.Counter: 这个映射类型会给键准备一个整数计数器。每次更新一个键的时候都会增加这个计数器。所以这个类型可以用来给可散列表对象计数,或者是当成多重集来用——多重集合就是集合里的元素可以出现不止一次。
跟
OrderedDict
、ChainMap
和Counter
这些开箱即用的类型不同,UserDict
是让用户继承写子类的。
dict
的实现及其导致的结果键必须是可散列的
一个可散列的对象必须满足以下要求。
(1) 支持
hash()
函数,并且通过__hash__()
方法所得到的散列值是不变的。(2) 支持通过
__eq__()
方法来检测相等性。(3) 若
a == b
为真,则hash(a) == hash(b)
也为真。所有由用户自定义的对象默认都是可散列的,因为它们的散列值由
id()
来获取,而且它们都是不相等的。字典在内存上的开销巨大
由于字典使用了散列表,而散列表又必须是稀疏的,这导致它在空间上的效率低下。
在用户自定义的类型中,
__slots__
属性可以改变实例属性的存储方式,由dict
变成tuple
键查询很快
dict
的实现是典型的空间换时间:字典类型有着巨大的内存开销,但它们提供了无视数据量大小的快速访问——只要字典能被装在内存里键的次序取决于添加顺序
当往
dict
里添加新键而又发生散列冲突的时候,新键可能会被安排存放到另一个位置。往字典里添加新键可能会改变已有键的顺序
无论何时往字典里添加新的键,Python 解释器都可能做出为字典扩容的决定。扩容导致的结果就是要新建一个更大的散列表,并把字典里已有的元素添加到新表里。
defaultdict默认值词典
错误方式
代码
1 | # 错误方式一 |
输出
1 | {'elliot': [91, 88], 'neelam': [98], 'bianca': [81]} |
正确方式
在这种情况下,你将创建一个defaultdict,它使用不带参数的list构造函数作为默认方法。没有参数的list返回一个空列表,因此如果名称不存在则defaultdict调用list(),然后再把学生成绩添加上。如果你想更炫一点,你也可以使用lambda函数作为值来返回任意常量。
在用户创建
defaultdict
对象的时候,就需要给它配置一个为找不到的键创造默认值的方法。实例化一个
defaultdict
的时候,需要给构造方法提供一个可调用对象把
list
构造方法作为default_factory
来创建一个defaultdict
。
defaultdict
里的default_factory
只会在__getitem__
里被调用,在其他的方法里完全不会发挥作用。比如,dd
是个defaultdict
,k
是个找不到的键,dd[k]
这个表达式会调用default_factory
创造某个默认值,而dd.get(k)
则会返回None
。所有的映射类型在处理找不到的键的时候,都会牵扯到
__missing__
方法。这也是这个方法称作“missing”的原因如果有一个类继承了
dict
,然后这个继承类提供了__missing__
方法,那么在__getitem__
碰到找不到的键的时候,Python 就会自动调用它
__missing__
方法只会被__getitem__
调用像
k in my_dict.keys()
这种操作在 Python 3 中是很快的,而且即便映射类型对象很庞大也没关系。这是因为dict.keys()
的返回值是一个“视图”。视图就像一个集合,而且跟字典类似的是,在视图里查找一个元素的速度很快。代码
1 | # 正确方式 |
输出
1 | defaultdict(<class 'list'>, {'elliot': [91, 88], 'neelam': [98], 'bianca': [81]}) |
统计缺失多少键
1 | class CountMissing: |
其它默认值
代码
1 | # 错误方式 |
输出
1 | The Man with No Name |
有序词典OrderedDict
概念
OrderedDict类似于正常的词典,只是它记住了元素插入的顺序,当在有序的词典上迭代时,返回的元素就是它们第一次添加的顺序。
class collections.OrderedDict,返回已给dict的子类,支持常规的dict的方法,OrderedDict是一个记住元素首次插入顺序的词典,如果一个元素重写已经存在的元素,那么原始的插入位置保持不变,如果删除一个元素再重新插入,那么它就在末尾。
使用
使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序。如果要保持Key的顺序,可以用OrderedDict,OrderedDict的Key会按照插入的顺序排列,不是Key本身排序
例子1
代码
1 | # 例子1 |
输出
1 | False |
例子2
1 | # 例子3 |
1 | OrderedDict([('apple', 4), ('banana', 3)]) |
ChainMap
原始的映射对象被存放在一个列表中构成一个字典序列
self.maps
在
ChainMap
中查询某个键时,会对原始的映射对象依次查询,直至找到这个键,若未找到,则默认引发KeyError
异常。在
ChainMap
中进行插入、更新、删除时,只会对原始映射中的第一个映射进行操作ChainMap 对象除了 maps 属性外,还具有一个 parents 属性和 new_child(m=None) 方法。
parents 属性返回了一个不包含原始映射中的第一个映射的 ChainMap 对象,对应源码中 self.class(self.maps[1:]) ,其效果和 ChainMap(d.maps[1:]) 相同
new_child(m=None) 方法返回一个包含指定映射 m(未指定时,为空字典)及其他原有映射的 ChainMap 对象,其中指定映射位于底层 maps 列表首位,在其他原有映射之前。
1 | # ChainMap 类是为了将多个映射快速的链接到一起,这样它们就可以作为一个单元处理。它通常比创建一个新字典和多次调用 update() 要快很多 |
使用
ChainMap
对象作为嵌套上下文我们在上面提到了
new_child(m=None)
可用于创建子上下文,这个是一个非常便捷的方法,可以使用一个空字典或其他指定的映射来创建一个新的ChainMap
对象。针对这个新对象的修改不会对原有的ChainMap
对象(可以将其理解为底层的数据结构或者基础上下文)产生影响。
1
2
3
4
5
6
7
8
9
10
11
12
13 'name': 'bob', 'age': 25} d_1 = {
'height': '175', 'weight': 120} d_2 = {
# 创建一个基础上下文 c = ChainMap(d_1, d_2)
# 创建一个嵌套的子上下文 c_nc = c.new_child()
'skill'] = 'Python' # 在子上下文环境中进行赋值 c_nc[
c
ChainMap({'name': 'bob', 'age': 25}, {'height': '175', 'weight': 120})
c_nc
ChainMap({'skill': 'Python'}, {'name': 'bob', 'age': 25}, {'height': '175', 'weight': 120})
list(c_nc)
['height', 'weight', 'name', 'age', 'skill']
dict(c_nc)
{'height': '175', 'weight': 120, 'name': 'bob', 'age': 25, 'skill': 'Python'}
计数器Counter
Ps: Counter仅支持Hashable对象进行统计
定义
假如你有一长串没有标点符号或大写字母的单词,你想要计算每个单词出现的次数。
你可以使用字典或defaultdict增加计数,但collections.Counter提供了一种更清晰,更方便的方法。
Counter是dict的子类,它使用0作为任何缺失元素的默认值,并且更容易计算对象的出现次数:
1 | from collections import Counter |
遍历元素
遍历所有元素
1 | # 当你将单词列表传递给Counter时,它会存储每个单词以及该单词在列表中出现的次数。 |
1 | [('there', 4), ('was', 4)] |
更新元素
1 | # update(增加元素) |
1 | Counter({'there': 4, 'was': 4, 'if': 3, 'not': 2, 'but': 1, 'you': 1, 'are': 1, 'here': 1}) |
类集合操作
1 | c = Counter(a=3, b=1) |
1 | Counter({'a': 4, 'b': 3}) |
子类化UserDict
就创造自定义映射类型来说,以
UserDict
为基类,总比以普通的dict
为基类要来得方便。而更倾向于从
UserDict
而不是从dict
继承的主要原因是,后者有时会在某些方法的实现上走一些捷径,导致我们不得不在它的子类中重写这些方法,但是UserDict
就不会带来这些问题。值得注意的地方是,
UserDict
并不是dict
的子类,但是UserDict
有一个叫作data
的属性,是dict
的实例,这个属性实际上是UserDict
最终存储数据的地方。
双向队列deque
定义
deque是栈和队列的一种广义实现,deque是”double-end queue”的简称;deque支持线程安全、有效内存地以近似O(1)的性能在deque的两端插入和删除元素,尽管list也支持相似的操作,但是它主要在固定长度操作上的优化,从而在pop(0)和insert(0,v)(会改变数据的位置和大小)上有O(n)的时间复杂度。
常用方法
deque支持如下方法,
append(x), 将x添加到deque的右侧;
appendleft(x), 将x添加到deque的左侧;
clear(), 将deque中的元素全部删除,最后长度为0;
count(x), 返回deque中元素等于x的个数;
extend(iterable), 将可迭代变量iterable中的元素添加至deque的右侧;
extendleft(iterable), 将变量iterable中的元素添加至deque的左侧,往左侧添加序列的顺序与可迭代变量iterable中的元素相反;
pop(), 移除和返回deque中最右侧的元素,如果没有元素,将会报出IndexError;
popleft(), 移除和返回deque中最左侧的元素,如果没有元素,将会报出IndexError;
remove(value), 移除第一次出现的value,如果没有找到,报出ValueError;
reverse(), 反转deque中的元素,并返回None;
rotate(n), 从右侧反转n步,如果n为负数,则从左侧反转,d.rotate(1)等于d.appendleft(d.pop());
maxlen, 只读的属性,deque的最大长度,如果无解,就返回None;
除了以上的方法之外,deque还支持迭代、序列化、len(d)、reversed(d)、copy.copy(d)、copy.deepcopy(d),通过in操作符进行成员测试和下标索引,索引的时间复杂度是在两端是O(1),在中间是O(n),为了快速获取,可以使用list代替。
index(查找某个元素的索引位置)
insert(在指定位置插入元素)
1 | from collections import deque |
1 | # deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈: |
multiprocessing:这个包实现了自己的Queue,它和queue.Queue类似,是设计给进程间通信使用的。同时还有个multiprocessing.JoinableQueue类型,可以让任务管理变得更方便。
asyncio:python3.4新提供的包,里面有Queue、LifoQueue、PriorityQueue和JoinableQueue,这些类受到queue和mulitiprocessing模块的影响,但是为异步编程里的任务管理提供了便利。
子类化UserDict
UserDict |
封装了字典对象,简化了字典子类化 |
---|---|
UserList |
封装了列表对象,简化了列表子类化 |
UserString |
封装了列表对象,简化了字符串子类化 |