2.37. 命名元组与双端队列¶
列表、元组、字典和集合覆盖了大多数数据需求。collections 模块中另有三种容器,适用于那些内置类型处理起来很别扭的特定问题。
2.37.1. namedtuple —— 无需类的带类型记录¶
普通元组按位置存储值。对于一个小巧、生命周期短的 2 元组或 3 元组来说这没问题,但再往后 point[0] 和 point[1] 就开始掩盖它们究竟存储了什么。collections.namedtuple() 返回一个新的元组子类,其字段带有名称:
>>> from collections import namedtuple
>>> Reading = namedtuple('Reading', ('temp', 'humidity', 'ts'))
>>> r = Reading(22.5, 41.0, 137204)
>>> r.temp
22.5
>>> r.humidity
41.0
>>> r[0]
22.5
字段参数是一个名称字符串的序列(在 CPython 中也可以是一个以空白分隔的字符串;MicroPython 更严格——请传入一个元组或列表)。
为什么使用 namedtuple 而不是类?
它是元组。迭代、解包、相等比较、哈希以及用作字典键,全都可以直接使用。
它是不可变的。重新赋值
r.temp = ...会引发AttributeError,而这正是你对记录类型所期望的。它比具有相同字段的类实例占用更少的 RAM——元组的存储是连续的,没有
__dict__。
与等价的类相比,一条 namedtuple 声明只需一行。其代价是字段是只读的——要“修改”一个读数,你需要新建一个。
2.37.2. deque —— 有界环形缓冲区¶
列表在 末端(append / pop)操作很快,而在 起始端(insert(0, ...) / pop(0) 都会移动其他每一个元素)很慢。collections.deque 在两端都很快——它是一个由头指针和尾指针索引的环形缓冲区,因此无论双端队列持有多少元素,在任一端进行追加和弹出都耗费相同的固定工作量。
在 MicroPython 中构造时同时需要一个初始可迭代对象 和 一个最大长度,且顺序如此:
>>> from collections import deque
>>> events = deque((), 5)
>>> for i in range(8):
... events.append(i)
>>> list(events)
[3, 4, 5, 6, 7]
当一个有界双端队列已满时,每次 append 都会丢弃最旧的元素。这使得双端队列非常适合“最近 N 个样本”“最近 N 行日志”或任何无需无限增长的滚动窗口。
MicroPython 的 deque 所暴露的方法刻意保持精简:
append(x)—— 添加到右端。appendleft(x)—— 添加到左端。extend(iterable)—— 从可迭代对象中逐个追加每个元素。pop()—— 移除并返回右端元素。为空时引发IndexError。popleft()—— 移除并返回左端元素。
相比 CPython 的 deque 值得注意的缺失项:没有 clear、count、index、remove、reverse、rotate、maxlen 属性或 __contains__。迭代和下标索引可以使用:
>>> events[0]
3
>>> for e in events:
... print(e)
一个典型用途:保留最近几个传感器读数以检测趋势变化:
history = deque((), 10)
def push(reading):
history.append(reading)
if len(history) == 10 and history[-1] > 2 * history[0]:
print('reading is climbing')
2.37.3. OrderedDict —— 当顺序是相等性的一部分时¶
自 MicroPython 1.13 和 CPython 3.7 起,普通的 dict 已保留插入顺序。这涵盖了人们过去使用 collections.OrderedDict 的最常见原因。
OrderedDict 仍能提供而普通字典无法提供的是:
OrderedDict的相等比较会考虑顺序。两个普通字典只要拥有相同的键/值对就比较为相等,与插入顺序无关。两个OrderedDict实例只有在其键值对顺序相同时才相等:>>> from collections import OrderedDict >>> OrderedDict([('a', 1), ('b', 2)]) == OrderedDict([('b', 2), ('a', 1)]) False >>> {'a': 1, 'b': 2} == {'b': 2, 'a': 1} True
当你要将配置序列化为一种 确实 关心键顺序的格式(TOML、某些 YAML 消费者)时,或者当你想给代码的读者一个清晰的文档提示来说明顺序很重要时,
OrderedDict就很有用。
对于日常代码,请优先使用内置的 dict。只有当“顺序是相等性一部分”的语义或文档价值真正带来好处时,才选用 OrderedDict。
这三种容器各有一个狭窄的适用场景。命名元组取代手工编写的记录类;双端队列在有界队列场景下取代列表;有序字典则让插入顺序成为契约的一部分。