collections
官网翻译为容器数据类型
,在Java中称为集合框架,名字不同而已。Python内置了一些数据类型,dict , list , set , 和 tuple
。某些情况下这些数据容器不够用,我们得继承基本数据容器,然后实现定制化的功能。比如原始的字典是无序的,假如需要有序的字典怎么办?集合框架collections
就提供了OrderedDict
这样的数据容器。除此之外集合框架提供了以下类型:
名称 | 含义 |
---|---|
namedtuple() | 创建命名元组子类的工厂函数 |
deque | 类似列表(list)的容器,实现了在两端快速添加(append)和弹出(pop) |
ChainMap | 类似字典(dict)的容器类,将多个映射集合到一个视图里面 |
Counter | 字典的子类,提供了可哈希对象的计数功能 |
OrderedDict | 字典的子类,保存了他们被添加的顺序 |
defaultdict | 字典的子类,提供了一个工厂函数,为字典查询提供一个默认值 | |
UserDict | 封装了字典对象,简化了字典子类化 |
UserList | 封装了列表对象,简化了列表子类化 |
UserString | 封装了列表对象,简化了字符串子类化 |
namedtuple 命名元组
tuple创建以后是不可更改的,访问tuple只能使用下标索引的方式。凡是用Python连接过数据库的都知道,查询数据库返回的结果是tuple类型。如果字段较多,超过5个字段,操作就有点麻烦。
如果使用ORM框架可以很好的解决这一痛点。类直接映射表,类的属性就是表的字段,用“点”就能补全出要使用的字段名称。除此之外我们还可以用namedtuple
。尤其是创建CSV文件和读取数据库时非常有效。
我们先来看看namedtuple
函数的基本使用:
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(11, y=22) # instantiate with positional or keyword arguments
>>> p[0] + p[1] # indexable like the plain tuple (11, 22)
33
>>> x, y = p # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y # fields also accessible by name
33
>>> p # readable __repr__ with a name=value style
Point(x=11, y=22)
>>> p._asdict() # 转换为字典
{'x': 11, 'y': 22}
接下来模拟数据库查询,遍历返回结果,会发现操作每条数据就像操作对象一样方便。这里会用到方法_make()
创建新的实例。
from collections import namedtuple
# 数据库返回结果
data = (
('Tom', 12, 'tom@qq.com'),
('Jim', 15, 'jim@163.com'),
('Ana', 13, 'Ana@126.com')
)
User = namedtuple('User', ['name', 'age', 'email'])
# 类型声明
user: User
for user in map(User._make, data):
print(f"{user.name}, {user.age}, {user.email}")
# 输出
# Tom, 12, tom@qq.com
# Jim, 15, jim@163.com
# Ana, 13, Ana@126.com
读取CSV文件和数据库的使用,跟上一个例子差不多,下面给出源码
EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade')
# 最好做类型声明,使用的时候可以点出属性
emp: EmployeeRecord
import csv
for emp in map(EmployeeRecord._make, csv.reader(open("employees.csv", "rb"))):
print(emp.name, emp.title)
import sqlite3
conn = sqlite3.connect('/companydata')
cursor = conn.cursor()
cursor.execute('SELECT name, age, title, department, paygrade FROM employees')
for emp in map(EmployeeRecord._make, cursor.fetchall()):
print(emp.name, emp.title)
计数器Counter
作为计数器对象,Counter非常方便,提供了几个统计方法非常好用。我们先来看看普通计数器的使用:
>>> from collections import Counter
>>> cnt = Counter()
>>> for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']:
... cnt[word] += 1 # 不需要考虑key是否覆盖
>>> cnt
Counter({'blue': 3, 'red': 2, 'green': 1})
除了创建空的计数器,还可以创建初始化参数,参数就是要统计的对象,字符串、列表、或者字典等可迭代类型。
>>> c = Counter('abcdeabcdabcaba') # count elements from a string
>>> c.most_common(3) # three most common elements
[('a', 5), ('b', 4), ('c', 3)]
>>> sorted(c) # list all unique elements
['a', 'b', 'c', 'd', 'e']
>>> ''.join(sorted(c.elements())) # list elements with repetitions
'aaaaabbbbcccdde'
默认字典defaultdict
访问字典key
的时候会碰到一个问题,如果key存在就正常返回,如果key不存在则抛出KeyError
的异常。通常使用字典的get
方法,如果getkey
不存在,默认值可以自定义,但这只能解决不抛出KeyError
的异常。其余问题还是不好处理。
比如我们有一个清单是这样的,颜色使用次数。(你也可以理解成用户的购物明细数据)
s = [
('yellow', 1),
('blue', 2),
('yellow', 3),
('blue', 4),
('red', 1)
]
如果要统计出每种颜色使用了多少次,大致的思路是创建结果字典,遍历s,碰到新的key就直接赋值,碰到已经存在的key,value值相加就可以了。当然也可以用上面的Counter类,判断的步骤都省了。
现在需求变了,需要把每种颜色使用的次数放在列表里。就是把相同的key的value合并在一起,不是加在一起了。当然你肯定也有办法做到。这里我们使用defaultdict
使得代码更加pythonic
。
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d = defaultdict(list) # 默认值为list
for k, v in s:
d[k].append(v) # 直接append
sorted(d.items())
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]
上面的例子defaultdict
的默认值是list
,也可以是整数,其实是调用了int()
。
s = 'mississippi'
d = defaultdict(int) # 默认整数,初始值为0
for k in s:
d[k] += 1
sorted(d.items())
[('i', 4), ('m', 1), ('p', 2), ('s', 4)]
如果需要自定义默认数据怎么做呢?我们需要清楚defaultdict
初始化的原理。查看源码,我们发现有一个参数default_factory
来控制的。这个参数的取值可以是None,或者是callable
可调用的类型。这就非常简单了,default_factory
参数传一个函数就行了。
>>> x = defaultdict(lambda : 2) # 创建默认值为2
>>>x['a']
2
>>> x['b']
2
>>> y = defaultdict(lambda : [2]) # 创建默认值为[2]
>>> y['t']
[2]
总结
Python集合框架提供的这几种数据容器,并不是必须的,实现某些功能也可以不用这些数据容器,如果业务量比较大,执行效率可能不是很高。本文只介绍了Python的集合框架中的3种数据容器,这3种容器相对麻烦一点,使用场景很多,灵活运用这些数据容器,可以使得你程序更加高效和pythonic。
注:本文参考Python3官方文档,部分例子也来自官方文档。