介绍
collections
模块是 Python 标准库的一部分,提供了多种增强的数据类型,包括 namedtuple
、deque
、Counter
、OrderedDict
、defaultdict
和 ChainMap
。
这些数据类型比内置类型更灵活高效,适用于特定场景。掌握和合理使用这些数据结构,你会发现日常开发效率会非常高。
常见数据结构
namedtuple
namedtuple
是 Python collections
模块中的一个工厂函数,用于创建具有命名字段的不可变序列。它提供了类似于元组的性能和内存效率,同时可以通过名称访问其元素,从而提高代码的可读性和可维护性。
下面通过一段关于操作 地理坐标(Geolocation) 的代码来详细说明:如何创建 namedtuple
、访问其元素,以及 namedtuple
的方法和属性。
# 导入
from collections import namedtuple
# 定义 namedtuple 类型 Geo
Geo = namedtuple('Geo', ['latitude', 'longitude'])
# 创建几个 Geo 实例
a_geo = Geo(39.9042, 116.4074)
b_geo = Geo(31.2304, 121.4737)
c_geo = Geo(23.1291, 113.2644)
# 打印地理坐标
print(f"a_geo: {a_geo.latitude}, {a_geo.longitude}") # 输出: a_geo: 39.9042, 116.4074
print(f"b_geo: {b_geo.latitude}, {b_geo.longitude}") # 输出: b_geo: 31.2304, 121.4737
print(f"c_geo: {c_geo.latitude}, {c_geo.longitude}") # 输出: c_geo: 23.1291, 113.2644
# 使用 _make() 方法创建实例
data = [22.5431, 114.0579]
d_geo = Geo._make(data)
print(f"d_geo: {d_geo.latitude}, {d_geo.longitude}") # 输出: d_geo: 22.5431, 114.0579
# 使用 _asdict() 方法将 namedtuple 转换为字典
a_dict = a_geo._asdict()
print(a_dict) # 输出: OrderedDict([('latitude', 39.9042), ('longitude', 116.4074)])
# 使用 _replace() 方法更新实例
updated_a = a_geo._replace(latitude=39.913818)
print(updated_a) # 输出: Geo(latitude=39.913818, longitude=116.4074)
# 查看字段名称
print(Geo._fields) # 输出: ('latitude', 'longitude')
应用场景
- 数据记录:用于表示数据库查询结果或日志记录。
- 配置项:用于保存配置信息,例如服务器配置或应用程序设置。
- 数据传输:用于定义消息格式,方便序列化和传输数据。
deque
deque
是一个双端队列,它支持从两端进行高效的添加和删除操作。
基本使用
deque 支持从两端进行快速的添加和删除操作。
from collections import deque
# 创建一个空的 deque
my_deque = deque()
# 创建包含元素的 deque
my_deque_with_data = deque([1, 2, 3, 4, 5])
# 添加元素到 deque 的右端
my_deque.append(6)
# 添加元素到 deque 的左端
my_deque.appendleft(0)
# 从 deque 的右端删除元素
right_element = my_deque.pop()
# 从 deque 的左端删除元素
left_element = my_deque.popleft()
deque 的方法和属性
-
append(item)
: 将元素添加到 deque 的右端。 -
appendleft(item)
: 将元素添加到 deque 的左端。 -
pop()
: 从 deque 的右端删除并返回一个元素。 -
popleft()
: 从 deque 的左端删除并返回一个元素。 -
extend(iterable)
: 将可迭代对象中的元素添加到 deque 的右端。 -
extendleft(iterable)
: 将可迭代对象中的元素添加到 deque 的左端。 -
rotate(n)
: 将 deque 向右循环移动 n 步(如果 n 是负数,则向左移动)。 -
clear()
: 清空 deque 中的所有元素。 -
count(item)
: 返回 deque 中等于 item 的元素个数。 -
reverse()
: 将 deque 中的元素逆序排列。 -
copy()
: 返回 deque 的浅拷贝。
我们来用这些方法实现一个基于LRU策略(最近最少使用)的缓存类来慢慢感受下deque
的高级玩法:
from collections import deque
class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.cache = deque(maxlen=capacity) # 使用 deque 作为缓存
self.cache_map = {} # 用于存储键值对的映射关系
def get(self, key):
if key in self.cache_map:
# 如果键存在于缓存中,则将其移到缓存队列的右端(最近使用)
self.cache.remove(key)
self.cache.append(key)
return self.cache_map[key]
else:
return -1
def put(self, key, value):
if key in self.cache_map:
# 如果键已存在于缓存中,则更新值并将其移到缓存队列的右端(最近使用)
self.cache.remove(key)
self.cache.append(key)
self.cache_map[key] = value
else:
if len(self.cache) == self.capacity:
# 如果缓存已满,则移除最左端(最久未使用)的键值对
removed_key = self.cache.popleft()
del self.cache_map[removed_key]
# 将新的键值对添加到缓存队列的右端(最近使用)
self.cache.append(key)
self.cache_map[key] = value
def clear(self):
# 清空缓存队列和映射关系
self.cache.clear()
self.cache_map.clear()
def extend(self, iterable):
# 将可迭代对象中的元素添加到缓存队列的右端(最近使用)
self.cache.extend(iterable)
def __repr__(self):
return str(self.cache)
# 测试示例
cache = LRUCache(3)
cache.put(1, 'a')
cache.put(2, 'b')
cache.put(3, 'c')
print(cache) # 输出: deque([1, 2, 3], maxlen=3)
cache.put(4, 'd')
print(cache)
# 输出: deque([2, 3, 4], maxlen=3)
# 因为元素超过了3个,所以淘汰了元素 1,添加保留了元素 4
cache.clear()
print(cache) # 输出: deque([], maxlen=3)
cache.extend([4, 5, 6])
print(cache) # 输出: deque([4, 5, 6], maxlen=3)
应用场景
- 队列和栈:用于实现队列(FIFO)和栈(LIFO)等数据结构。
- 缓存:用于实现LRU(Least Recently Used)缓存算法,保留最近访问的元素,丢弃最旧的元素。
- 任务调度:用于实现异步任务调度器,管理任务队列并支持快速的入队和出队操作。
Counter
常用于计数可哈希对象。
基本使用
from collections import Counter
# 创建一个 Counter 对象
my_counter = Counter([1, 1, 2, 3, 3, 3, 4, 4, 5])
# 获取元素的计数
count_of_3 = my_counter[3] # 输出: 3
# 更新计数
my_counter[3] += 1
# 添加新元素
my_counter[6] = 1
方法和属性
-
elements()
: 返回一个迭代器,包含 Counter 对象中的所有元素,重复次数与计数相等。 -
most_common(n)
: 返回前 n 个最常见的元素及其计数,以列表形式返回。 -
subtract(iterable)
: 从 Counter 对象中减去可迭代对象中的元素的计数。 -
update(iterable)
: 将可迭代对象中的元素添加到 Counter 对象中。 -
clear()
: 清空 Counter 对象中的所有元素。 -
copy()
: 返回 Counter 对象的浅拷贝。 -
items()
: 返回 Counter 对象的键值对。 -
keys()
: 返回 Counter 对象的键。 -
values()
: 返回 Counter 对象的值。 -
most_common(n)
: 返回前 n 个最常见的元素及其计数。
你会发现Counter
有部分类似dict
字段的方法,这是因为Counter
类继承了dict
类
通过实现一个打工人出生地的统计计数器来感受下Counter
类的使用:
from collections import Counter
# 模拟一堆大学生的出生地数据
birthplaces = ['Beijing', 'Shanghai', 'Guangzhou', 'Beijing', 'Shanghai']
# 创建 Counter 对象统计出生地
birthplace_counter = Counter(birthplaces)
# 获取出生地为上海的学生人数
shanghai_students = birthplace_counter['Shanghai']
# 更新计数,添加更多学生的出生地数据
more_birthplaces = ['Beijing', 'Shanghai', 'Chongqing', 'Shanghai', 'Shenzhen']
birthplace_counter.update(more_birthplaces)
# 获取出生地为北京的学生人数
beijing_students = birthplace_counter['Beijing']
# 统计有哪些地区
uniq_birthplaces = list(birthplace_counter.keys())
# 获取计数最多的 2 个出生地及其计数
top_birthplaces = birthplace_counter.most_common(2)
# 从统计中减去一部分出生地数据
subtract_birthplaces = ['Beijing', 'Shanghai']
birthplace_counter.subtract(subtract_birthplaces)
# 获取计数最少的 1 个出生地及其计数
bottom_birthplace = birthplace_counter.most_common()[-1]
# 清空计数器
birthplace_counter.clear()
# 输出结果
print("出生地为上海的学生人数:", shanghai_students) # 出生地为上海的学生人数: 2
print("出生地为北京的学生人数:", beijing_students) # 出生地为北京的学生人数: 3
print("有哪些出生地:", uniq_birthplaces)
# 有哪些出生地: ['Beijing', 'Shanghai', 'Guangzhou', 'Chongqing', 'Shenzhen']
print("计数最多的 2 个出生地:", top_birthplaces)
# 计数最多的 2 个出生地: [('Shanghai', 4), ('Beijing', 3)]
print("计数最少的 1 个出生地:", bottom_birthplace) # 计数最少的 1 个出生地: ('Shenzhen', 1)
print("清空后的计数器:", birthplace_counter) # 清空后的计数器: Counter()
应用场景
- 文本处理:用于统计单词出现次数或字符出现次数。
- 数据分析:用于统计数据集中各个元素的出现频率。
- 词频统计:用于生成词云、绘制频率分布图等。
OrderedDict
OrderedDict
是一个有序字典,它可以记住元素的添加顺序。虽然 Python3.7 以后版本的dict
也改成了有序字典,但基于可读性和兼容性,依然使用推荐OrderedDict
。
基本使用
from collections import OrderedDict
# 创建一个空的 OrderedDict
empty_ordered_dict = OrderedDict()
# 创建包含键值对的 OrderedDict
ordered_dict = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
# 添加键值对到 OrderedDict
ordered_dict['d'] = 4
# 删除键值对
del ordered_dict['a']
# 获取键对应的值
value_of_b = ordered_dict['b']
OrderedDict 的方法和属性
-
move_to_end(key, last=True)
: 将指定键移动到有序字典的最后或最开始,默认移到最后。 -
popitem(last=True)
: 弹出有序字典中的最后一个键值对或第一个键值对,默认弹出最后一个。 -
clear()
: 清空有序字典中的所有键值对。 -
copy()
: 返回有序字典的浅拷贝。 -
keys()
: 返回有序字典中的所有键。 -
values()
: 返回有序字典中的所有值。 -
items()
: 返回有序字典中的所有键值对。
OrderedDict
和Counter
一样,也是继承了dict
一样
应用场景
- 配置文件:用于保存配置信息,并保持配置项的顺序与文件中的顺序一致。
- 历史记录:用于记录用户操作历史,并保持操作的顺序。
- 命令行参数:用于保存命令行参数,并保持参数的顺序与输入顺序一致。
defaultdict
defaultdict
是一种字典的子类,它允许给每个键一个默认值,从而避免了在访问不存在的键时引发 KeyError
异常,这在实际开发中非常有用
基本使用
from collections import defaultdict
# 创建一个默认字典,指定默认值为 int 类型的 0
default_dict = defaultdict(int)
# 添加键值对到 defaultdict
default_dict['a'] = 1
# 获取键对应的值,如果键不存在,则返回默认值, int 默认返回0
value_of_b = default_dict['b']
print(value_of_b) # 输出:0
# 删除键值对
# 当然删除不存在的键值对还是会触发 KeyError 的
del default_dict['a']
# 创建一个默认字典,指定默认值为 list 类型的空列表
default_dict_list = defaultdict(list)
print(default_dict_list["a"]) # 输出:[]
defaultdict 的方法和属性
-
default_factory
: 默认工厂函数,用于生成默认值。 -
copy()
: 返回 defaultdict 对象的浅拷贝。 -
keys()
: 返回 defaultdict 对象中的所有键。 -
values()
: 返回 defaultdict 对象中的所有值。 -
items()
: 返回 defaultdict 对象中的所有键值对。
defaultdict
也是继承了dict
,所以很多方法都很类似,区别在于defaultdict
允许给每个键一个默认值
重点说说defualtdict
中的default_factory
:
default_factory
除了设置为某种类型(如 int
、list
等)外,还可以将其设置为函数,以便在需要时动态生成默认值(任何对象:数值、字符串、类实例等),从而实现更灵活的功能。
下面则是使用这种方法构造默认采集配置:
from collections import defaultdict
# 定义默认的爬虫配置参数
def default_config():
return {
'user_agent': 'Mozilla/5.0 ***',
'timeout': 10,
'retry': 3,
'headers': {'Accept': 'text/html,application/json'},
'proxies': None
}
# 创建一个 defaultdict,并指定默认值的生成函数为 default_config
crawler_config = defaultdict(default_config)
print(crawler_config["baidu"]["timeout"]) # 输出:10
print(crawler_config["weixin"]["timeout"]) # 输出:10
应用场景
- 计数器初始化:用于创建计数器字典,并设置默认计数值为0。
- 数据分组:用于分组数据集,并将缺失的组初始化为空列表或其他默认值。
- 统计分析:用于统计数据集中各类别的频率,并将缺失的类别初始化为0。
ChainMap
用于将多个字典或映射组合在一起形成单个视图,适用于需要处理多个映射的情况。
方法与属性
-
new_child(m=None)
: 创建一个新的 ChainMap 对象,将参数 m(字典或映射)添加到链的开头。 -
parents
: 返回一个包含所有父映射的新 ChainMap 对象。 -
maps
: 返回一个包含所有映射的列表。 -
copy()
: 返回 ChainMap 对象的浅拷贝。
用法
下面举个配置合并的例子来说明下用法:
from collections import ChainMap
# 定义三个模块的配置信息
module1_config = {'timeout': 10, 'retry': 3}
module2_config = {'user_agent': 'Mozilla/5.0', 'proxies': 1}
module3_config = {'headers': {'Accept': 'text/html'}}
# 使用 ChainMap 合并多个模块的配置
combined_config = ChainMap(module1_config, module2_config, module3_config)
# 获取所有映射
print("所有映射:")
print(combined_config.maps)
# 获取父映射(去除最后一个映射)
parent_maps = combined_config.parents
print("\n父映射:")
for parent_map in parent_maps:
print(parent_map)
# 获取指定键的值
print("\n获取指定键的值:")
print("timeout:", combined_config['timeout'])
print("user_agent:", combined_config['user_agent'])
# 添加新的映射
new_module_config = {'timeout': 20, 'retry': 5}
combined_config = combined_config.new_child(new_module_config)
print("\n添加新的映射后的配置信息:")
print(combined_config)
print(combined_config['proxies'])
应用场景
- 配置管理:用于合并多个配置源(如全局配置、用户配置、默认配置)并提供统一的配置视图。
- 命名空间:用于将多个命名空间(如全局命名空间、模块命名空间、局部命名空间)组合在一起,形成单个命名空间。
- 上下文管理器:用于管理多个上下文,并提供统一的上下文视图,使得上下文的嵌套和覆盖更加灵活。
使用优势
-
更具表达性:
collections
中的数据结构通常比内置的数据结构更具表达性,能够更清晰地表达程序的意图,代码也会简单很多。 -
更高级的功能:
collections
中的数据结构提供了一些额外的功能,这些功能在特定的使用场景下非常有用。 -
性能优势:在某些特定的操作中,
collections
中的数据结构可能比内置数据结构性能更好。 -
可扩展性:如果你的程序需要更多的功能,
collections
模块提供了一些非常有用的数据结构,可以满足更广泛的需求。 -
标准化:使用
collections
中的数据结构能够让你的代码更加标准化和易于理解。
如果你觉得本文有帮助到你,非常期待得到你的支持和鼓励;
如果有其他问题或补充,欢迎评论区留言交流。