collections,Python开发更高效

介绍

collections 模块是 Python 标准库的一部分,提供了多种增强的数据类型,包括 namedtupledequeCounterOrderedDictdefaultdictChainMap

这些数据类型比内置类型更灵活高效,适用于特定场景。掌握和合理使用这些数据结构,你会发现日常开发效率会非常高。

常见数据结构

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(): 返回有序字典中的所有键值对。

OrderedDictCounter一样,也是继承了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除了设置为某种类型(如 intlist 等)外,还可以将其设置为函数,以便在需要时动态生成默认值(任何对象:数值、字符串、类实例等),从而实现更灵活的功能。

下面则是使用这种方法构造默认采集配置:

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中的数据结构能够让你的代码更加标准化和易于理解。

如果你觉得本文有帮助到你,非常期待得到你的支持和鼓励;
如果有其他问题或补充,欢迎评论区留言交流。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容