本文含 4818 字,6 图表截屏
建议阅读 26 分钟
本文是 Python 系列的特别篇的第十二篇
特别篇 11 - 异常处理
特别篇 12 - Collection
0
引言
我们在【盘一盘 Python 下】一贴介绍过 5 种类型的容器型(container)数据,分别是字符串(string)、列表(list)、元组(tuple)、字典(dictionary)和集合(set)。
顾名思义,容器型就是将一组元素打包在一起。有人觉得字符串不是容器型数据,但我认为它将一个个字符(character)打包在一起,因此也算是容器型数据。
今天我们来介绍除上面 5 个之外的容器型数据,统称 Collection,见下图。
上图中的 UserDict, UserList 和 UserString 只是字典、列表和字符串的一个简单封装,而 frozenset 就是“元素不可修改”的集合,它们平时使用的频率很少,因此略过不讲。
本帖只介绍 6 种使用较多(和上面 4 种较少的相比而言)的 Collection,它们是
namedtuple
defaultdict
Counter
deque
ChainMap
OrderedDict
再具体讲它们之前,我们来思考一个基础问题,为什么要创造它们?字符串列表元组字典集合这“五大金刚”不香吗?创造它们一定是五大金刚有缺陷,先看看它们之间的功能总结。
请思考:
元组的可读性太差但不可修改,字典的可读性强但可修改,两者一结合便是命名元组 (namedtuple)。
在读写字典时,如果键不存在会报错,那么就有了默认字典 (defaultdict)。
计数器 (Counter) 是个字典的子类,能快速计量元素出现的个数。
列表只能从尾部添加元素,不能从头部,那么就有了双端队列 (deque)。
在多个字典上操作效率不高,那么就有了链式映射 (ChainMap)。
原来普通字典在读取中不能记录放入元素的顺序(现在可以了),那么就有了有序字典 (OrderedDict)。因此我觉得有序字典现在用处不大,但还是讲讲吧。
你看带着这种思路学习新知识,是不是目的性更强一些,也更容易记住知识点。
1
namedtuple
命名元组 (namedtuple) 是元组和字典的混合体,它赋予每个元素含义 (字典的特性),而且元素不可修改 (元组的特性)。
创建命名元组需要设定两个必需参数:元组名称,字段名称。
FP = namedtuple('FP', ['asset', 'instrument'])
FP
__main__.FP
其中 FP 是 Financial Product 的缩写,我们发现 namedtuple 创建的和类 (class) 很像。
有了“类”,我们可以创建“对象”了,来试试创建一个外汇欧式期权(FX European Option),语法如下:
product = FP('FX', 'European Option')
product
FP(asset='FX', instrument='European Option')
像字典一样查看其键 (用 _fields),像元组一样查看其值的索引 (用 index) 和计数 (用 count)。
product._fields
('asset', 'instrument')
product.index('FX')
0
product.count('FX')
1
获取元素有三种方法:
像元祖那样用数值索引
像对象那样用点 .
像对象那样用函数 getattr
print( product[0] )
print( product.asset )
print( getattr(product, 'asset') )
FX
FX
FX
元组不可修改,命名元组也是,应该直接更新其元素会报错。
product.asset = 'IR'
但可以用 _replace 函数更新其值,并生成新的命名元组,比如把资产类别从外汇 FX 换到商品 CM。
product = product._replace(asset='CM')
product
FP(asset='CM', instrument='European Option')
用 _make 函数创建新的命名元组
product1 = FP._make(['IR', 'Basis Swap'])
product1
FP(asset='IR', instrument='Basis Swap')
还可以用字典打散的形式来创建命名元组
product2 = FP(**{'asset':'EQ', 'instrument':'Accumulator'})
product2
FP(asset='EQ', instrument='Accumulator')
2
defaultdict
默认字典 (defaultdict) 和字典不同,我们不需要检查键是否存在,对于不存在的键,会赋一个默认值。
issubclass(defaultdict, dict)
True
创建默认字典时候记住要先设定好其值类型,用 int 举例先。
nums = defaultdict(int)
nums['one'] = 1
nums['two'] = 2
nums['three'] = 3
print( nums['four'] )
print( nums )
0
defaultdict(<class 'int'>, {'one': 1, 'two': 2, 'three': 3, 'four': 0})
你看,字典 nums 没有 'four' 这个键,因此给它赋予整型变量的默认值 0,如果是浮点型变量,那么默认值是 0.0,如果是字符串,那么默认值是 ''。
再把类型设为 list 来举例。
def_dict = defaultdict(list)
def_dict['one'] = 1
def_dict['missing']
def_dict['another_missing'].append(4)
def_dict
defaultdict(list, {'one': 1, 'missing': [], 'another_missing': [4]})
当键为 'missing' 和 'another_missing' 时,空列表 [] 作为默认值赋给其键对应的值。
3
Counter
计数器 (Counter) 是字典的子类,提供了可哈希(hashable)对象的计数功能。可哈希就是可修改(mutable),比如列表就是可哈希或可修改。
issubclass(Counter, dict)
True
一个简单例子来看 Counter 怎么使用。
l = [1,2,3,4,1,2,6,7,3,8,1,2,2]
answer = Counter(l)
print(answer)
print(answer[2])
Counter({2: 4, 1: 3, 3: 2, 4: 1, 6: 1, 7: 1, 8: 1})
4
结果很直观,不用解释了。
4
deque
双端队列 (deque) 可让我们从头/尾两端添加或删除元素。
首先创建双端队列。
deq = deque([1, 10.31, 'Python'])
print(deq)
deque([1, 10.31, 'Python'])
我们知道列表里用 append,extend 和 pop 方法,它们只能从尾部添加或删除元素,那么在双端队列里有 appendleft, extendleft 和 popleft 方法,从左边,即尾部,添加或删除元素。看例子。
使用 append 和 appendleft,把参数来整块添加。
deq.append('OK')
deq.appendleft(True)
print(deq)
deque([True, 1, 10.31, 'Python', 'OK'])
使用 extend 和 extendleft ,把参数来打散添加,列表 ['Go', 0] 打散成两个元素添加上去,如果是 append 那么就把这个列表当成整体添加上去。
deq.extend(['Go',0])
deq.extendleft([None,'I'])
print(deq)
deque(['I', None, True, 1, 10.31, 'Python', 'OK', 'Go', 0])
使用 pop 和 popleft 来删除元素。
p1 = deq.pop()
p2 = deq.popleft()
print(p1, p2)
print(deq)
0 I
deque([None, True, 1, 10.31, 'Python', 'OK', 'Go'])
5
ChainMap
链式映射 (ChainMap) 可看成字典的容器,将多个映射串联起来,这样它们就可以作为一个单元处理。通常比创建一个新字典和多次调用 update 函数要快很多。
首先创建链式映射,先创建三个字典,再把它们打包成 ChainMap。
toys = {'San Guo Sha': 30, 'Monopoly': 20}
computers = {'Mac': 1000, 'Lenovo': 800, 'Acer': 400}
clothing = {'Jeans': 40, 'Tees': 10}
inventory = ChainMap( toys, computers, clothing )
inventory
ChainMap({'San Guo Sha': 30, 'Monopoly': 20},
{'Mac': 1000, 'Lenovo': 800, 'Acer': 400},
{'Jeans': 40, 'Tees': 10})
查看键大富豪, 'Monopoly' 对应的值。
inventory['Monopoly']
20
如果多个字典都有某个键,那么返回第一个含该键的字典的值。
用 get() 函数查看超级玛丽,'Super Mario',如果每个字典都没此键,不返回值也不报错。
inventory.get('Super Mario')
删除三国杀, 'San Guo Sha'。
p = inventory.pop('San Guo Sha')
print(p)
inventory
30
ChainMap({'Monopoly': 20},
{'Mac': 1000, 'Lenovo': 800, 'Acer': 400},
{'Jeans': 40, 'Tees': 10})
在 Toy 字典中添加任天堂,'Nintendo'。
toys['Nintendo'] = 200
inventory
ChainMap({'Monopoly': 20, 'Nintendo': 200},
{'Mac': 1000, 'Lenovo': 800, 'Acer': 400},
{'Jeans': 40, 'Tees': 10})
6
OrderedDict
有序字典 (OrderedDict) 是字典的子类,就像常规字典一样,它会记录放入元素的顺序,但现在常规字典也有这种功能了,因此有序字典的存在意义也不大了。
issubclass(OrderedDict, dict)
True
首先创建有序词典和常规字典,发现两者都是按着元素放入的顺序来记录的。
order = OrderedDict()
order['b'] = 1
order['a'] = 2
order['c'] = 3
print(order)
OrderedDict([('b', 1), ('a', 2), ('c', 3)])
unordered = dict()
unordered['b'] = 1
unordered['a'] = 2
unordered['c'] = 3
print(unordered)
{'b': 1, 'a': 2, 'c': 3}
在有序词典中,有一个 reversed() 函数,可以逆序返回字典的键。对比下面两个例子。
for key in order:
print( key, ':', order[key] )
b : 1
a : 2
c : 3
for key in reversed(order):
print( key, ':', order[key] )
c : 3
a : 2
b : 1
结果很直观,不解释了。
7
总结
就一张图,看那些连点的细节,不解释。
Stay Tuned!