0.摘要
本文主要介绍python3中collections模块的使用方法,并给出具体应用实例。
Python拥有一些内置的数据类型,比如str, int, list, tuple, dict等, collections模块在这些内置数据类型的基础上,提供了几个额外的数据类型:
- namedtuple(): 生成可以使用名字来访问元素内容的tuple子类
- deque: 双端队列,可以快速的从另外一侧追加和推出对象
- Counter: 计数器,主要用来计数
- OrderedDict: 有序字典
- defaultdict: 带有默认值的字典
1.namedtuple()
namedtuple(): 生成可以使用名字来访问元素内容的tuple子类
优势:通常的代码都是通过索引来访问列表或者元组的,但是这样的方式并不直观。如果能够通过名称访问元素,就能够减少结构对位置的依赖性。相对于普通的元组,collections.nameedtuple()只增加了极小的开销就提供了这一便利。
原理:collections.nameedtuple()是一个工厂方法,它返回的是python中标准元组类型的子类。即提供一个类型名称及相应字段,它就能够返回一个可实例化的类,并为定义好的字段传入值。
from collections import namedtuple
Subscriber = namedtuple('Subscriber',['addr','joined'])
sub = Subscriber('[email protected]','2012-10-19')
print(sub.addr)
print(sub.joined)
尽管namedtuple的实例看似一个普通的类实例,但是它的实例与普通的元组是可以互相转换的:
Subscriber = namedtuple('Subscriber',['addr','joined'])
sub = Subscriber('[email protected]','2012-10-19')
t1 = tuple(sub) #namedtuple实例转tuple
print(t1)
n1 = Subscriber(*t1) #tuple转namedtuple实例
print(n1)
同时,namedtuple的实例也支持普通元组所支持的操作,比如索引、分解等:
Subscriber = namedtuple('Subscriber',['addr','joined'])
sub = Subscriber('[email protected]','2012-10-19')
#求长度
print(len(sub))
#索引
print(sub[0])
#分解
addr, joined = sub
print(addr)
print(joined)
对于一般的元组,也可以通过转化为命名元组的方式,是的代码具有更强的表现力:
from collections import namedtuple
Stock = namedtuple('Stock', ['name', 'shares', 'price'])
def compute_cost(records):
total = 0.0
for rec in records:
s = Stock(*rec)
total += rec[1] *rec[2]
return total
# Some Data
records = [
('GOOG', 100, 490.1),
('ACME', 100, 123.45),
('IBM', 50, 91.15)
]
print(compute_cost(records))
注意:namedtuple与字典是不同的,namedtuple是不可变的(immutable)。
如果需要修改属性,可以使用_replace()方法实现。该方法会创建一个新的命名元组,从而实现替换。
Stock = namedtuple('Stock', ['name', 'shares', 'price','data', 'time'])
#Create a prototype instance
stock_prototype = Stock('', 0, 0.0, None, None)
#Function to convert a dictionary to a Stock
def dict_to_stock(s):
return stock_prototype._replace(**s)
a = {'name':'ACME', 'shares':100, 'price':123.45}
stock_a = dict_to_stock(a)
print(stock_a)
#result:Stock(name='ACME', shares=100, price=123.45, data=None, time=None)
2.deque()
deque: 双端队列,可以快速的从另外一侧追加和推出对象。
这时候读者可能会联想到,python中的list也具有这类功能,即使用insert和pop即可。但是,python中的list对象的实现这类功能的时间复杂度是 O(n) 。而使用deque对象则是 O(1) 的复杂度。所以,collections.deque()时间复杂度更低。
import collections
d = collections.deque() #创建一个双向队列
d.append(0) #在队列右端添加一个元素
d.appendleft(1) #在队列左端添加一个元素
d.clear() #清空队列
new_d = d.copy() #(浅)拷贝一个队列
d.count(1) #统计一个元素出现的次数
d.extend([1,2,3]) #批量向右扩展元素,参数必须为列表或元组(可以为空)
d.extendleft((4,5,6)) #批量相左扩展元素
i = d.index(1) #查找元素的第一个索引,从0开始
i = d.index(1,0,5) #在区间[0,5)中查找元素1的索引
d.insert(5,'hello') #在指定位置插入元素
d.pop() #弹出最右端元素
d.popleft() #弹出最左端元素
d.remove(1) #删除指定元素,若有多个,删除最左端的
d.reverse() #队列翻转
d.rotare() #取最右侧元素放置最左侧。括号中可指定次数,默认为一次。
利用deque,可以实现一些简单实用的功能,比如生成缓冲条:
import collections
import time
import os
d = collections.deque()
d.extend(['·','·','·','·','·','·','·','·','·','·','·','·'])
d.appendleft('>>>')
while (True):
d.rotate()
s = ''.join(d)
print('Loading')
print(s)
time.sleep(0.05)
os.system('cls') #清屏,Windows下使用cls命令,Linux和MacOS下使用clear命令
os.system('cls')需要在命令行下才能正常工作,在pycham中可能会出现显示异常的问题。
3.Counter()
Counter
是一个简单的计数器,例如,统计字符出现的个数:
words = [
'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the',
'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into',
'my', 'eyes', "you're", 'under'
]
word_counts = Counter(words)
print(word_counts)
#result:Counter({'eyes': 8, 'the': 5, 'look': 4, 'into': 3, 'my': 3, 'around': 2, 'not': 1, "don't": 1, "you're": 1, 'under': 1})
Counter实际上也是dict
的一个子类。
除了统计各元素数量之外,Counter.most_common()方法甚至可以直接告诉我们出现次数最多的元素:
top_three = word_counts.most_common(3)
print(top_three)
# outputs [('eyes', 8), ('the', 5), ('look', 4)]
如果想要统计两个列表中,元素数量总和,不需要将两个列表合并,直接调用.update()方法即可:
morewords = ['why','are','you','not','looking','in','my','eyes']
word_counts.update(morewords)
print(word_counts.most_common(3))
#result:[('eyes', 9), ('the', 5), ('look', 4)]
可见,morewords列表中的信息也被叠加统计到了word_counts这一实例中。
另外,Counter对象还支持数学操作:
a = Counter(words)
b = Counter(morewords)
print(a)
print(b)
#result:Counter({'eyes': 8, 'the': 5, 'look': 4, 'into': 3, 'my': 3, 'around': 2, 'not': 1, "don't": 1, "you're": 1, 'under': 1})
#result:Counter({'why': 1, 'are': 1, 'you': 1, 'not': 1, 'looking': 1, 'in': 1, 'my': 1, 'eyes': 1})
print(a - b) #若b中元素也存在于a中,则在a中减去相应次数
#result:Counter({'eyes': 7, 'the': 5, 'look': 4, 'into': 3, 'my': 2, 'around': 2, "don't": 1, "you're": 1, 'under': 1})
print(a + b)
#result:Counter({'eyes': 9, 'the': 5, 'look': 4, 'my': 4, 'into': 3, 'not': 2, 'around': 2, "don't": 1, "you're": 1, 'under': 1, 'why': 1, 'are': 1, 'you': 1, 'looking': 1, 'in': 1})
4.OrderedDict()
python中的字典是无序的,如果需要建立有序字典,可以考虑OrderedDict。当对字典做迭代的时候,它会严格按照元素初始添加的顺序进行。
from collections import OrderedDict
d = OrderedDict()
d['foo'] = 1
d['bar'] = 2
d['spam'] = 3
d['grok'] = 4
for key in d:
print(key)
当想构建一个映射结构以便稍后对其做序列化或者编码成另外一种格式时,OrderedDict就显得特别有用。例如,在使用JSON编码并需要精确控制个字段的顺序时。
import json
json.dump(d)
注意:由于OrderedDict内部维护了一个双向链表,它会根据元素加入的顺序来排列键的位置,新加入的元素被放置在链表的末尾,对已存在的键重新赋值不改变键的顺序。所以,OrderedDict的大小是普通字典的2倍多。
5.defaultdict()
collections.defaultdict()是用来创建多值字典的。
字典是一个关联容器,每个键映射到单独的值上,如果想要让一个键映射多个值,需要将多个值保存到另外一个容器(比如列表或集合)。因此,不借助defaultdict()方法也可以创建多值字典,比如:
pairs = [('language','Python'),('fiction','Three-Body Problem'),('language','C++')]
d = {}
for key, value in pairs:
if key not in d:
d[key] = []
d[key].append(value)
print(d)
#result:{'language': ['Python', 'C++'], 'fiction': ['Three-Body Problem']}
这里可以看出,我们每次需要判断key是否已经存在。但是,使用defaultdict就可以避免这样情况,并且代码更加简洁:
from collections import defaultdict
pairs = [('language','Python'),('fiction','Three-Body Problem'),('language','C++')]
d = defaultdict(list)
for key, value in pairs:
d[key].append(value)
print(d)
#result:{'language': ['Python', 'C++'], 'fiction': ['Three-Body Problem']}
如果希望保留元素插入的顺序,就用defaultdict(list);
如果希望去除重复元素,就用defaultdict(set)