namedtuple
OrderedDict defaultdict
deque
Counter
ChainMap

collections中的高级数据结构

先来看看collections模块中包含的数据结构,查看源码如下:

__all__ = ['deque', 'defaultdict', 'namedtuple', 'UserDict', 'UserList',
            'UserString', 'Counter', 'OrderedDict', 'ChainMap']

这里主要介绍其中的数据结构:deque, defaultdict, namedtuple, Counter, OrderedDict和ChainMap。且其中更常用且需要重点掌握的是namedtuple,defaultdict。

回顾tuple

  • 定义及初始化
    • tuple(),()
    • tuple(iterable)如tuple(range(3))
    • t=(1,)
    • t=(1,)*5
  • 方法
    • t[index]
    • t.index(value[,start,[stop]])
    • t.count(value)
    • len(t)
  • 拆包
user_tuple = ('jerry', 12)
name, age = user_tuple
print(name, age)
user_tuple = ('jerry', 12, 'male')
name, *other_info = user_tuple
print(name, other_info)

namedtuple

命名元组namedtuple(typename, field_names, *, rename=False, defaults=None, module=None)

  • 返回一个元组的子类,并定义了字段
  • field_names可以是空白符或者逗号分割的字段的字符串,可以是字段的列表
# 回顾类实例的属性访问
class User:
    def __init__(self, name, age, *other_info):
        self.name = name
        self.age = age
        self.other_info = other_info

j = User('jerry', 12, 'male')
print(j, j.age, j.name, j.other_info)
# 用命名元组实现
from collections import namedtuple

# User = namedtuple('User', ('name', 'age'))
# User = namedtuple('User', 'name age')
# User = namedtuple('User', 'name,age')
User = namedtuple('User', ['name', 'age'])  # 上面几种方式都可以,一般常用列表
print(User, type(User))
j = User('jerry', 28)  # 或j = User(name='jerry', age=28)
print(j, type(j))
print(j.name, j.age)
# j.age = 30 # AttributeError: can't set attribute # namedtuple是tuple的子类,tuple的特性,不可变
name, age = j  # 拆包
print(name, age)

# namedtuple是tuple的子类,因此可使用tuple相关的方法
print(1, j.__class__, j.__class__.__base__)
print(j.index(28))
print(len(j))
  • namedtuple实例化
# namedtuple实例化
User = namedtuple('User', ['name', 'age', 'sex'])
j1 = User('jerry', 28, 'male')  # 方式1
j2 = User(name='jerry', age=28, sex='male')  # 方式2
user_tuple = ('jerry', 28, 'male')
user_dict = {
    'name': 'jerry',
    'age': 28,
    'sex': 'male'
}

j3 = User(*user_tuple)  # 方式3
j4 = User(**user_dict)  # 方式4
print(j1, j2, j3, j4, sep='\n')

User = namedtuple('User', ['name', 'age', 'sex', 'height'])
j5 = User(*user_tuple, height=170)
print(j5)
  • _make(cls, iterable)_asdict(self)
User = namedtuple('User', ['name', 'age', 'sex'])
user_dict = {
    'name': 'jerry',
    'age': 28,
    'sex': 'male'
}

user_tuple = ('jerry', 28, 'male')
j = User('jerry', 28, 'male')

print(User._make(user_dict.values()))
print(User._make(user_tuple))
print(j._asdict(), type(j._asdict()))

_make()可接受一个可迭代对象作为参数可不用拆包;_asdict()是类的实例方法,可将返回一个字典

回顾dict

dict相关知识可参看我之前的博文python学习笔记:字典dict
几点额外补充:

  • py3.6字典是有序的
  • py3.8可使用reversed来反转dict
  • py3.9新增合并运算
jerry_info = {'name': 'jerry', 'age': 28}
jerry_other_info = {'height': 170, 'sex': 'male'}

jerry_extra = reversed(jerry_other_info)
print(type(jerry_extra))
# print(next(jerry_extra))
print(list(jerry_extra))
jerry_other_info = dict(reversed(jerry_other_info.items()))
print(jerry_other_info)

jerry_about = jerry_info | jerry_other_info  # 执行合并运算时,如果字典包含相同的 key, 运算结果将采用第二个字典的键值对
print(jerry_about)

jerry_info |= jerry_other_info
print(jerry_info)
# |= 操作符另外一个功能是使用一个可迭代对象的键值对更新字典
update_info = ((i, i ** 2) for i in range(3))
jerry_info |= update_info
print(jerry_info)

OrderedDict 和 defaultdict

  • OrderedDict
    • class OrderedDict(dict):
    • py3.6 版本字典就是有序的,如为兼容老版本py,可使用 OrderedDict
    • 且OrderedDict还额外提供popitem(self, last=True)和move_to_end(self, key, last=True)更方便的操作
  • defaultdict([default_factory[, ...]])
    • 第一个参数 default_factory,缺省是 None,它提供一个初始化函数。当 key 不存在的时候,会调用这个工厂函数来生成 key 对应的 value(生成的是value)
from collections import OrderedDict

user_dict = {'name': 'jerry', 'age': 28, 'sex': 'male', 'height': 170, 'salary': 999999}
user_ordered_dict = OrderedDict(user_dict)
print(1, user_ordered_dict)
print(2, user_ordered_dict['name'])
print(3, user_ordered_dict.pop('sex'))
print(4, user_ordered_dict)
print(5, user_ordered_dict.popitem())
print(6, user_ordered_dict)
user_ordered_dict.move_to_end('age')
print(7, user_ordered_dict)

先看一个例子:统计一个list中的不同元素的个数

# 常规做法
num_list = [1, 2, 2, 'a', 1, 'b', 'a']
num_dict = {}
for i in num_list:
    if i not in num_dict:
        num_dict[i] = 1
    else:
        num_dict[i] += 1
print(num_dict)
# 利用dict的setdefault,避免使用太多的if判断
num_list = [1, 2, 2, 'a', 1, 'b', 'a']
num_dict = {}
for i in num_list:
    num_dict.setdefault(i, 0)
    num_dict[i] += 1
print(num_dict)
  • 可不可以进一步简化?
# 使用defaultdict来简化
from collections import defaultdict
num_list = [1, 2, 2, 'a', 1, 'b', 'a']
num_dict = defaultdict(int)
# print(num_dict) # defaultdict(<class 'int'>, {})
for i in num_list:
    num_dict[i] += 1
print(num_dict) # defaultdict(<class 'int'>, {1: 2, 2: 2, 'a': 2, 'b': 1})
  • 进一步理解defaultdict,例子:生成固定结构的字典
from collections import defaultdict

def gen():
    return {
        'name': 'jerry',
        'age': 28
    }
user_dict = defaultdict(gen)
print(user_dict['base_info'])

deque

  • 双端队列,可在队列两头操作
  • 用c实现,比list相关方法性能更优
from collections import deque

l1 = [28, 170, 'male']
l2 = ['tester']
l3 = ['No.1']
l_deque = deque(l1)
l_deque.append('master')
l_deque.appendleft('jerry')
print(l_deque)
l_deque.extend(l2)
print(l_deque)
l_deque.extendleft(l3)
print(l_deque)
l_deque.pop()
print(l_deque)
l_deque.popleft()
print(l_deque)

更多的方法可参考源码

Counter

Dict subclass for counting hashable items. Sometimes called a bag or multiset. Elements are stored as dictionary keys and their counts are stored as dictionary values.

  • class Counter(dict): def __init__(self, iterable=None, /, **kwds): 主要用于计数
  • dict子类,可使用dict相关操作
  • most_common(n):返回top n 的元素数据,从大到小
from collections import Counter

s1 = 'abcdeabcdabcaba'
s2 = ['a', '1', '2', 'a', '1', 'b']
# c1 Counter({'a': 4, 'b': 2})  # a new counter from a mapping
# c2 = Counter(a=4, b=2) # a new counter from keyword args
s1_count = Counter(s1)  # 从多到少排序
s2_count = Counter(s2)
print(1, s1_count, s2_count, type(s1_count))
# Counter为dict的子类
print(2, s1_count['a'])  # 字典访问
s1_count.update(s2_count)  # 字典操作
print(3, s1_count)
print(4, s1_count.elements(), s1_count.values(), s1_count.keys())
print(5, sorted(s1_count))  # 去重后的list
print(6, s1_count.most_common(2))  # top2的数据

ChainMap

将多个映射快速的链接到一起,这样它们就可以作为一个单元处理

# 例1:ChainMap的属性和方法
from collections import ChainMap

d1 = {'name': 'jerry', 'age': 28}
d2 = {'name': 'tom', 'sex': 'male'}  # 与d1有相同的k-v
d = ChainMap(d1, d2)
print(1, d)
for k, v in d.items():
    print(k, v)  # d1,d2若有相同的k-v,则值打印d1中的

print(2, d.maps)  # maps属性,一个可以更新的映射列表
d.maps[0]['age'] = 30
print(3, d, d1)  # d1也被修改
print(4, d.new_child())
d3 = d.new_child({'edu': 'master'})
print(5, d, d3)  # d不变
print(6, d3.parents)
d['name'] = 'tina'  # 对于字典的更新或删除操作总是影响的是列表中第一个字典
print(7, d)
d1['name'] = 'lily'  # 一个 ChainMap 通过引用合并底层映射。 所以,如果一个底层映射更新了,这些更改会反映到 ChainMap
print(8, d)

# 例2:假设你必须在两个字典中执行查找操作 (比如先从 a 中找,如果找不到再在 b中找)
a = {'x': 8, 'z': 7}
b = {'y': 2, 'z': 4}
c = ChainMap(a, b)
print(c, c.get('x'), c.get('y'), c.get('z'))

其他更多方法或属性可参看源码