盘一盘 Python 系列特别篇 - Collection


本文含 4818 字,6 图表截屏

建议阅读 26 分钟

本文是 Python 系列的特别篇的第十二篇

0

引言

我们在【盘一盘 Python 下】一贴介绍过 5 种类型的容器型(container)数据,分别是字符串(string)、列表(list)、元组(tuple)、字典(dictionary)和集合(set)。

顾名思义,容器型就是将一组元素打包在一起。有人觉得字符串不是容器型数据,但我认为它将一个个字符(character)打包在一起,因此也算是容器型数据。

今天我们来介绍除上面 5 个之外的容器型数据,统称 Collection,见下图。

上图中的 UserDict, UserList 和 UserString 只是字典、列表和字符串的一个简单封装,而 frozenset 就是“元素不可修改”的集合,它们平时使用的频率很少,因此略过不讲。

本帖只介绍 6 种使用较多(和上面 4 种较少的相比而言)的 Collection,它们是

  1. namedtuple

  2. defaultdict

  3. Counter

  4. deque

  5. ChainMap

  6. 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!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值