如果一个列表需要频繁从头部插入或删除,list 往往不是最合适的工具。Python 标准库中的 collections.deque 专门面向双端操作:可以快速地从左端或右端追加、弹出元素,也能用同一套接口构建队列、栈和定长缓存。
理解 deque 的关键,不是记住几个方法名,而是判断数据应该从哪一端进入、从哪一端离开,以及是否需要让容器自动淘汰旧数据。
两端操作是核心
deque 的名字来自 double-ended queue,表示“双端队列”。它支持以下常用操作:
append(value):从右端追加元素appendleft(value):从左端追加元素pop():从右端移除并返回元素popleft():从左端移除并返回元素
这些操作适合处理需要先进先出、后进先出,或者需要同时操作两端的场景。与其把列表的头部当作队列出口,不如直接使用 popleft() 表达意图。
用同一个 deque 构建队列和栈
队列通常遵循先进先出(FIFO):元素从右端进入,从左端离开。栈则遵循后进先出(LIFO):元素从右端进入,也从右端离开。
下面的例子可以直接运行,分别演示两种用法:
from collections import deque
# 队列:先进先出
queue = deque(["task-1", "task-2"])
queue.append("task-3")
print(queue.popleft()) # task-1
print(queue) # deque(['task-2', 'task-3'])
# 栈:后进先出
stack = deque()
stack.append("page-home")
stack.append("page-products")
stack.append("page-detail")
print(stack.pop()) # page-detail
print(stack) # deque(['page-home', 'page-products'])
这里的选择很明确:队列使用 append() 和 popleft(),栈使用 append() 和 pop()。如果业务代码中出现大量 list.pop(0),可以考虑改成 deque.popleft(),让数据结构和访问模式保持一致。
maxlen:自动维护最近 N 个元素
创建 deque 时可以指定 maxlen,让它只保留固定数量的元素。当新元素加入已满的双端队列时,另一端最旧的元素会自动被移除。
from collections import deque
recent_events = deque(maxlen=3)
for event in ["login", "view", "search", "logout"]:
recent_events.append(event)
print(list(recent_events))
# 输出:
# ['login']
# ['login', 'view']
# ['login', 'view', 'search']
# ['view', 'search', 'logout']
这个行为适合实现最近操作记录、滚动窗口和有限大小的事件缓存。需要注意的是,自动淘汰发生在插入时,代码不会收到额外的回调或异常;如果旧数据不能被静默丢弃,就不应只依赖 maxlen。
从左端插入时,淘汰方向也会相应改变:
from collections import deque
numbers = deque([2, 3, 4], maxlen=3)
numbers.appendleft(1)
print(numbers) # deque([1, 2, 3])
做题时的判断清单
遇到 deque 相关问题,可以按下面的顺序检查:
- 数据是否需要从两端频繁添加或删除?如果是,考虑
deque。 - 是否是先进先出?使用
append()配合popleft()。 - 是否是后进先出?使用
append()配合pop()。 - 是否只保留最近的固定数量?创建时设置
maxlen。 - 是否需要随机访问中间元素?如果访问重点是索引和中间位置,列表可能更直接;
deque的优势集中在两端操作。
掌握这些组合后,deque 的大多数基础题目都可以转化为一个简单问题:元素从哪一端进,从哪一端出。先确定这个方向,再选择对应的方法,代码通常会更短,也更容易验证。