Python 内置模块 collections
collections 模块是 Python 的内置模块,提供了许多有用的集合类型,这些类型是对 Python 基本数据类型(如 dict、list、set 和 tuple)的扩展和增强。该模块中的类型能够帮助我们更高效地处理数据结构相关的任务。下面请看对 collections 模块的详细介绍。
import collections
print(dir(collections))
Counter - 计数器
Counter 基本用法
Counter
是一个字典的子类,用于计算可哈希对象的数量。它是一个无序的集合,其中元素存储为字典的键,它们的计数存储为值。
from collections import Counter
# 统计字符串中每个字符的出现次数
text = "hello world"
char_count = Counter(text)
print(char_count) # Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
# 统计列表中每个元素的出现次数
fruits = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
fruit_count = Counter(fruits)
print(fruit_count) # Counter({'apple': 3, 'banana': 2, 'cherry': 1})
Counter 的创建方式
from collections import Counter
# 1. 从可迭代对象创建
counter1 = Counter([1, 2, 3, 1, 2, 1])
print("从列表创建:", counter1)
# 2. 从字典创建
counter2 = Counter({'a': 3, 'b': 2, 'c': 1})
print("从字典创建:", counter2)
# 3. 从关键字参数创建
counter3 = Counter(cats=4, dogs=2, birds=1)
print("从关键字参数创建:", counter3)
# 4. 创建空的 Counter
counter4 = Counter()
print("空 Counter:", counter4)
Counter 的常用方法
from collections import Counter
# 创建一个 Counter 对象
votes = Counter(['A', 'B', 'C', 'A', 'B', 'A', 'C', 'A', 'B'])
# most_common() - 返回最常见的元素和它们的计数
print("最常见的2个元素:", votes.most_common(2)) # [('A', 4), ('B', 3)]
print("所有元素按频率排序:", votes.most_common())
# elements() - 返回一个迭代器,包含所有元素
print("所有元素:", list(votes.elements()))
# total() - 返回所有计数的总和 (Python 3.10+)
# print("总计数:", votes.total())
# subtract() - 减去计数
votes.subtract(['A', 'B'])
print("减去后的计数:", votes)
# update() - 增加计数
votes.update(['A', 'A', 'D'])
print("增加后的计数:", votes)
Counter 的数学运算
from collections import Counter
counter1 = Counter(['a', 'b', 'c', 'a', 'b', 'b'])
counter2 = Counter(['a', 'b', 'b', 'd'])
print("counter1:", counter1) # Counter({'b': 3, 'a': 2, 'c': 1})
print("counter2:", counter2) # Counter({'b': 2, 'a': 1, 'd': 1})
# 加法 - 合并计数
print("加法:", counter1 + counter2) # Counter({'b': 5, 'a': 3, 'c': 1, 'd': 1})
# 减法 - 减去计数(保留正数)
print("减法:", counter1 - counter2) # Counter({'b': 1, 'a': 1, 'c': 1})
# 交集 - 取最小值
print("交集:", counter1 & counter2) # Counter({'a': 1, 'b': 2})
# 并集 - 取最大值
print("并集:", counter1 | counter2) # Counter({'b': 3, 'a': 2, 'c': 1, 'd': 1})
defaultdict - 默认字典
defaultdict 基本概念
defaultdict
是字典的子类,当访问不存在的键时,会自动创建该键并赋予默认值。默认值由传入的工厂函数生成。
from collections import defaultdict
# 使用 list 作为默认工厂函数
dd_list = defaultdict(list)
dd_list['fruits'].append('apple')
dd_list['fruits'].append('banana')
dd_list['vegetables'].append('carrot')
print(dd_list) # defaultdict(<class 'list'>, {'fruits': ['apple', 'banana'], 'vegetables': ['carrot']})
print(dd_list['meat']) # [],自动创建空列表
不同类型的默认值
from collections import defaultdict
# 使用 int 作为默认值(默认为0)
dd_int = defaultdict(int)
text = "hello world"
for char in text:
dd_int[char] += 1
print("字符计数:", dict(dd_int))
# 使用 set 作为默认值
dd_set = defaultdict(set)
dd_set['numbers'].add(1)
dd_set['numbers'].add(2)
dd_set['letters'].add('a')
print("集合字典:", dict(dd_set))
# 使用 str 作为默认值
dd_str = defaultdict(str)
dd_str['greeting'] += 'Hello'
dd_str['greeting'] += ' World'
print("字符串字典:", dict(dd_str))
自定义默认值函数
from collections import defaultdict
# 使用 lambda 函数
dd_lambda = defaultdict(lambda: "N/A")
dd_lambda['name'] = 'Alice'
print(dd_lambda['name']) # Alice
print(dd_lambda['age']) # N/A
# 使用自定义函数
def default_list_with_zero():
return [0]
dd_custom = defaultdict(default_list_with_zero)
dd_custom['scores'].append(95)
dd_custom['scores'].append(87)
print("自定义默认值:", dict(dd_custom)) # {'scores': [0, 95, 87]}
print("新键的默认值:", dd_custom['new_key']) # [0]
OrderedDict - 有序字典
OrderedDict 基本用法
OrderedDict
是字典的子类,它记住了元素插入的顺序。在 Python 3.7+ 中,普通字典也保持插入顺序,但 OrderedDict
提供了额外的功能。
from collections import OrderedDict
# 创建有序字典
od = OrderedDict()
od['first'] = 1
od['second'] = 2
od['third'] = 3
print("有序字典:", od)
print("键的顺序:", list(od.keys()))
# 从元组列表创建
items = [('name', 'Alice'), ('age', 25), ('city', 'New York')]
od2 = OrderedDict(items)
print("从列表创建:", od2)
OrderedDict 特有方法
from collections import OrderedDict
od = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
# popitem() - 默认删除最后一个项目
last_item = od.popitem()
print("删除的最后项目:", last_item) # ('d', 4)
print("剩余项目:", od)
# popitem(last=False) - 删除第一个项目
od['d'] = 4 # 重新添加
first_item = od.popitem(last=False)
print("删除的第一项目:", first_item) # ('a', 1)
print("剩余项目:", od)
# move_to_end() - 移动到末尾
od.move_to_end('b')
print("移动b到末尾:", od)
# move_to_end(last=False) - 移动到开头
od.move_to_end('c', last=False)
print("移动c到开头:", od)
OrderedDict 与普通字典的比较
from collections import OrderedDict
# 普通字典
regular_dict1 = {'a': 1, 'b': 2}
regular_dict2 = {'b': 2, 'a': 1}
print("普通字典相等性:", regular_dict1 == regular_dict2) # True
# 有序字典
ordered_dict1 = OrderedDict([('a', 1), ('b', 2)])
ordered_dict2 = OrderedDict([('b', 2), ('a', 1)])
print("有序字典相等性:", ordered_dict1 == ordered_dict2) # False
# 有序字典在相等性比较时考虑顺序
ordered_dict3 = OrderedDict([('a', 1), ('b', 2)])
print("相同顺序的有序字典:", ordered_dict1 == ordered_dict3) # True
namedtuple - 命名元组
namedtuple 基本概念
namedtuple
是一个工厂函数,用于创建具有命名字段的tuple子类。它创建的类型既有元组的不可变性,又有通过名称访问字段的便利性。
from collections import namedtuple
# 定义一个命名元组类型
Person = namedtuple('Person', ['name', 'age', 'city'])
# 创建实例
person1 = Person('Alice', 30, 'New York')
person2 = Person(name='Bob', age=25, city='London')
print("person1:", person1)
print("通过索引访问:", person1[0]) # Alice
print("通过名称访问:", person1.name) # Alice
print("通过属性访问年龄:", person1.age) # 30
namedtuple 的字段定义方式
from collections import namedtuple
# 方式1:使用列表
Point1 = namedtuple('Point', ['x', 'y'])
# 方式2:使用字符串(空格分隔)
Point2 = namedtuple('Point', 'x y')
# 方式3:使用字符串(逗号分隔)
Point3 = namedtuple('Point', 'x,y')
# 所有方式创建的类型都相同
p1 = Point1(1, 2)
p2 = Point2(3, 4)
p3 = Point3(5, 6)
print("点1:", p1)
print("点2:", p2)
print("点3:", p3)
namedtuple 的常用方法
from collections import namedtuple
# 定义一个学生类型
Student = namedtuple('Student', 'name age grade')
student = Student('Alice', 20, 'A')
# _asdict() - 转换为字典
student_dict = student._asdict()
print("转为字典:", student_dict) # {'name': 'Alice', 'age': 20, 'grade': 'A'}
# _replace() - 创建新实例并替换指定字段
new_student = student._replace(age=21, grade='A+')
print("替换后:", new_student) # Student(name='Alice', age=21, grade='A+')
print("原对象:", student) # Student(name='Alice', age=20, grade='A')
# _fields - 查看字段名
print("字段名:", Student._fields) # ('name', 'age', 'grade')
# _make() - 从可迭代对象创建实例
data = ['Bob', 19, 'B']
student2 = Student._make(data)
print("从列表创建:", student2) # Student(name='Bob', age=19, grade='B')
deque - 双端队列
deque 基本概念
deque
(双端队列)是一个类似列表的容器,支持在两端快速添加和删除元素。它比列表在头部操作时更高效。
from collections import deque
# 创建双端队列
dq = deque([1, 2, 3, 4, 5])
print("初始队列:", dq)
# 右端添加和删除
dq.append(6) # 右端添加
print("右端添加6:", dq)
right_item = dq.pop() # 右端删除
print("右端删除的元素:", right_item)
print("删除后:", dq)
# 左端添加和删除
dq.appendleft(0) # 左端添加
print("左端添加0:", dq)
left_item = dq.popleft() # 左端删除
print("左端删除的元素:", left_item)
print("删除后:", dq)
deque 的限制长度
from collections import deque
# 创建限制长度的双端队列
limited_dq = deque(maxlen=3)
# 添加元素
for i in range(5):
limited_dq.append(i)
print(f"添加{i}后:", limited_dq)
# 输出:
# 添加0后: deque([0], maxlen=3)
# 添加1后: deque([0, 1], maxlen=3)
# 添加2后: deque([0, 1, 2], maxlen=3)
# 添加3后: deque([1, 2, 3], maxlen=3) # 0被自动移除
# 添加4后: deque([2, 3, 4], maxlen=3) # 1被自动移除
deque 的其他方法
from collections import deque
dq = deque([1, 2, 3, 4, 5])
# extend() 和 extendleft()
dq.extend([6, 7])
print("右端扩展:", dq) # deque([1, 2, 3, 4, 5, 6, 7])
dq.extendleft([0, -1])
print("左端扩展:", dq) # deque([-1, 0, 1, 2, 3, 4, 5, 6, 7])
# rotate() - 旋转队列
dq_rotate = deque([1, 2, 3, 4, 5])
dq_rotate.rotate(2) # 向右旋转2位
print("向右旋转2位:", dq_rotate) # deque([4, 5, 1, 2, 3])
dq_rotate.rotate(-1) # 向左旋转1位
print("向左旋转1位:", dq_rotate) # deque([5, 1, 2, 3, 4])
# clear() - 清空队列
dq.clear()
print("清空后:", dq) # deque([])
# copy() - 浅拷贝
original = deque([1, 2, 3])
copied = original.copy()
print("原队列:", original)
print("拷贝队列:", copied)
ChainMap - 链式映射
ChainMap 基本概念
ChainMap
将多个字典或映射组合成一个单一的、可更新的视图。它不会合并字典,而是创建一个查找链,按顺序搜索底层映射。
from collections import ChainMap
# 创建多个字典
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
dict3 = {'c': 5, 'd': 6}
# 创建链式映射
chain = ChainMap(dict1, dict2, dict3)
print("链式映射:", chain)
print("所有键:", list(chain.keys()))
print("所有值:", list(chain.values()))
# 查找值(按顺序查找,返回第一个找到的值)
print("键'a'的值:", chain['a']) # 1 (来自dict1)
print("键'b'的值:", chain['b']) # 2 (来自dict1,虽然dict2也有'b')
print("键'c'的值:", chain['c']) # 4 (来自dict2,虽然dict3也有'c')
print("键'd'的值:", chain['d']) # 6 (来自dict3)
ChainMap 的修改操作
from collections import ChainMap
dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'd': 4}
chain = ChainMap(dict1, dict2)
print("修改前:")
print("dict1:", dict1)
print("dict2:", dict2)
print("chain:", chain)
# 修改操作只影响第一个字典
chain['a'] = 10 # 修改dict1中的'a'
chain['e'] = 5 # 在dict1中添加新键'e'
print("\n修改后:")
print("dict1:", dict1) # {'a': 10, 'b': 2, 'e': 5}
print("dict2:", dict2) # {'c': 3, 'd': 4} - 未改变
print("chain:", chain)
# 删除操作
del chain['b'] # 从dict1中删除'b'
print("\n删除'b'后:")
print("dict1:", dict1) # {'a': 10, 'e': 5}
ChainMap 的方法
from collections import ChainMap
dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'd': 4}
dict3 = {'e': 5, 'f': 6}
chain = ChainMap(dict1, dict2)
# maps - 获取所有映射的列表
print("所有映射:", chain.maps) # [{'a': 1, 'b': 2}, {'c': 3, 'd': 4}]
# new_child() - 在前面添加新的空字典
new_chain = chain.new_child()
print("添加空字典后:", new_chain.maps) # [{}, {'a': 1, 'b': 2}, {'c': 3, 'd': 4}]
# new_child(dict) - 在前面添加指定字典
new_chain2 = chain.new_child(dict3)
print("添加dict3后:", new_chain2.maps) # [{'e': 5, 'f': 6}, {'a': 1, 'b': 2}, {'c': 3, 'd': 4}]
# parents - 获取除第一个映射外的新ChainMap
parent_chain = new_chain2.parents
print("父链映射:", parent_chain.maps) # [{'a': 1, 'b': 2}, {'c': 3, 'd': 4}]
ChainMap 实际应用
from collections import ChainMap
import os
# 模拟配置优先级:命令行参数 > 环境变量 > 默认配置
def get_config():
# 默认配置
defaults = {
'host': 'localhost',
'port': 8080,
'debug': False,
'timeout': 30
}
# 环境变量配置
env_config = {
'host': os.environ.get('HOST', ''),
'port': int(os.environ.get('PORT', 0)) if os.environ.get('PORT') else 0,
'debug': os.environ.get('DEBUG', '').lower() == 'true'
}
# 移除空值
env_config = {k: v for k, v in env_config.items() if v}
# 命令行参数配置(模拟)
cmd_config = {
'debug': True,
'port': 3000
}
# 创建配置链(优先级从高到低)
config = ChainMap(cmd_config, env_config, defaults)
return config
# 获取最终配置
final_config = get_config()
print("最终配置:")
for key, value in final_config.items():
print(f" {key}: {value}")
用户自定义容器类
UserDict - 字典包装类
UserDict
提供了一个字典的包装类,便于创建自定义字典类型。
from collections import UserDict
class CaseInsensitiveDict(UserDict):
"""不区分大小写的字典"""
def __setitem__(self, key, value):
# 将键转换为小写存储
super().__setitem__(key.lower(), value)
def __getitem__(self, key):
# 查找时也转换为小写
return super().__getitem__(key.lower())
def __contains__(self, key):
# 检查是否包含键时也转换为小写
return super().__contains__(key.lower())
def __delitem__(self, key):
# 删除时也转换为小写
super().__delitem__(key.lower())
# 测试不区分大小写的字典
ci_dict = CaseInsensitiveDict()
ci_dict['Name'] = 'Alice'
ci_dict['AGE'] = 30
ci_dict['City'] = 'New York'
print("不区分大小写字典:", ci_dict.data) # {'name': 'Alice', 'age': 30, 'city': 'New York'}
print("访问'name':", ci_dict['name']) # Alice
print("访问'NAME':", ci_dict['NAME']) # Alice
print("访问'Name':", ci_dict['Name']) # Alice
print("'AGE' in dict:", 'AGE' in ci_dict) # True
print("'age' in dict:", 'age' in ci_dict) # True
UserList - 列表包装类
from collections import UserList
class NumberList(UserList):
"""只允许数字的列表"""
def append(self, item):
if not isinstance(item, (int, float)):
raise TypeError("只允许添加数字")
super().append(item)
def insert(self, index, item):
if not isinstance(item, (int, float)):
raise TypeError("只允许插入数字")
super().insert(index, item)
def extend(self, items):
for item in items:
if not isinstance(item, (int, float)):
raise TypeError("只允许添加数字")
super().extend(items)
def sum(self):
"""计算列表中所有数字的和"""
return sum(self.data)
def average(self):
"""计算平均值"""
if not self.data:
return 0
return sum(self.data) / len(self.data)
# 测试数字列表
num_list = NumberList([1, 2, 3])
num_list.append(4)
num_list.extend([5, 6])
print("数字列表:", num_list.data) # [1, 2, 3, 4, 5, 6]
print("总和:", num_list.sum()) # 21
print("平均值:", num_list.average()) # 3.5
UserString - 字符串包装类
from collections import UserString
class ReversibleString(UserString):
"""可反转的字符串类"""
def reverse(self):
"""反转字符串"""
return ReversibleString(self.data[::-1])
def is_palindrome(self):
"""检查是否是回文,正读倒着读都一样"""
clean_data = self.data.lower().replace(' ', '')
return clean_data == clean_data[::-1]
def count_vowels(self):
"""计算元音字母数量"""
vowels = 'aeiouAEIOU'
return sum(1 for char in self.data if char in vowels)
# 测试可反转字符串
rev_str = ReversibleString("Hello World")
print("原字符串:", rev_str) # Hello World
print("反转字符串:", rev_str.reverse()) # dlroW olleH
print("是否是回文:", rev_str.is_palindrome()) # False
print("元音字母数量:", rev_str.count_vowels()) # 3
# 测试回文
palindrome = ReversibleString("A man a plan a canal Panama")
print("回文测试:", palindrome.is_palindrome()) # True
性能对比和最佳实践
性能对比
import time
from collections import defaultdict, deque
def performance_comparison():
"""比较不同数据结构的性能"""
# 列表 vs deque 在头部插入的性能对比
print("=== 列表 vs deque 头部插入性能对比 ===")
# 测试列表头部插入
start_time = time.time()
test_list = []
for i in range(10000):
test_list.insert(0, i)
list_time = time.time() - start_time
# 测试deque头部插入
start_time = time.time()
test_deque = deque()
for i in range(10000):
test_deque.appendleft(i)
deque_time = time.time() - start_time
print(f"列表头部插入10000次: {list_time:.4f}秒")
print(f"deque头部插入10000次: {deque_time:.4f}秒")
print(f"deque比列表快 {list_time/deque_time:.1f} 倍")
# 普通字典 vs defaultdict 性能对比
print("\n=== 普通字典 vs defaultdict 性能对比 ===")
# 测试普通字典
start_time = time.time()
normal_dict = {}
for i in range(10000):
key = f"key_{i % 100}"
if key not in normal_dict:
normal_dict[key] = []
normal_dict[key].append(i)
normal_dict_time = time.time() - start_time
# 测试defaultdict
start_time = time.time()
default_dict = defaultdict(list)
for i in range(10000):
key = f"key_{i % 100}"
default_dict[key].append(i)
default_dict_time = time.time() - start_time
print(f"普通字典分组: {normal_dict_time:.4f}秒")
print(f"defaultdict分组: {default_dict_time:.4f}秒")
print(f"defaultdict比普通字典快 {normal_dict_time/default_dict_time:.1f} 倍")
performance_comparison()
最佳实践建议
from collections import Counter, defaultdict, deque, namedtuple
def best_practices_examples():
"""Collections模块最佳实践示例"""
print("=== Collections 最佳实践 ===")
# 1. 使用Counter进行频率统计
print("1. 使用Counter进行统计:")
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
# 好的做法
word_count = Counter(words)
print(" 最常见的词:", word_count.most_common(2))
# 2. 使用defaultdict避免KeyError
print("\n2. 使用defaultdict分组数据:")
students = [("Alice", "Math"), ("Bob", "English"), ("Alice", "Science")]
# 好的做法
groups = defaultdict(list)
for name, subject in students:
groups[name].append(subject)
print(" 学生课程:", dict(groups))
# 3. 使用deque实现高效队列
print("\n3. 使用deque实现队列:")
queue = deque()
# 添加任务
for task in ["task1", "task2", "task3"]:
queue.append(task)
# 处理任务
while queue:
current_task = queue.popleft()
print(f" 处理任务: {current_task}")
# 4. 使用namedtuple创建轻量级类
print("\n4. 使用namedtuple创建数据结构:")
Point = namedtuple('Point', 'x y')
points = [Point(1, 2), Point(3, 4), Point(5, 6)]
total_x = sum(p.x for p in points)
total_y = sum(p.y for p in points)
print(f" 点的总和: x={total_x}, y={total_y}")
# 5. 合理选择数据结构
print("\n5. 数据结构选择建议:")
recommendations = {
"频率统计": "使用 Counter",
"分组数据": "使用 defaultdict",
"队列操作": "使用 deque",
"轻量级数据类": "使用 namedtuple",
"保持插入顺序": "使用 OrderedDict (Python < 3.7)",
"链式查找": "使用 ChainMap",
"自定义容器": "使用 UserDict/UserList/UserString"
}
for use_case, recommendation in recommendations.items():
print(f" {use_case}: {recommendation}")
best_practices_examples()
总结
collections
模块为Python提供了强大的容器数据类型,每种类型都有其特定的使用场景:
Counter
: 适用于计数和频率统计defaultdict
: 适用于避免KeyError和数据分组OrderedDict
: 适用于需要保持插入顺序的场景(Python 3.7+中普通字典已保持顺序)namedtuple
: 适用于创建轻量级、不可变的数据结构deque
: 适用于需要高效双端操作的队列场景ChainMap
: 适用于多层配置和变量查找UserDict/UserList/UserString
: 适用于创建自定义容器类型