Python deque 实战:高效实现队列、栈与定长历史记录

2026-08-18 27 预计阅读时间: 1 分钟
来源: realpython.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:6 分钟

当程序需要频繁从序列两端插入或删除元素时,普通 list 往往不是最合适的容器。Python 标准库 collections 提供的 deque,专门支持在头部和尾部高效执行追加与弹出操作,适合实现队列、栈以及有容量上限的历史记录。

为什么两端操作适合使用 deque

list.append()list.pop() 很适合处理列表尾部,但从头部插入或删除元素通常需要移动其余元素。deque 的名称来自 double-ended queue,也就是“双端队列”;它把两端都作为主要操作入口。

常用方法可以分成两组:

操作位置 添加元素 删除并返回元素
右端 append(value) pop()
左端 appendleft(value) popleft()

下面的程序可以直接运行,观察元素如何从两端进入和离开:

from collections import deque

items = deque(['task-2', 'task-3'])

items.append('task-4')
items.appendleft('task-1')
print('追加后:', list(items))

left_item = items.popleft()
right_item = items.pop()

print('左端取出:', left_item)
print('右端取出:', right_item)
print('剩余元素:', list(items))

运行命令:

python deque_demo.py

预期输出为:

追加后: ['task-1', 'task-2', 'task-3', 'task-4']
左端取出: task-1
右端取出: task-4
剩余元素: ['task-2', 'task-3']

需要注意,空的 deque 调用 pop()popleft() 会抛出 IndexError。消费元素前可以判断容器是否为空,或者在业务层明确处理异常。

用同一个容器实现队列和栈

队列遵循先进先出(FIFO)。生产者从右端加入任务,消费者从左端取出任务即可:

from collections import deque

queue = deque()

for task in ['resize-image', 'send-email', 'write-log']:
    queue.append(task)

while queue:
    task = queue.popleft()
    print(f'执行任务: {task}')

栈遵循后进先出(LIFO)。只操作 deque 的右端,就能得到栈的行为:

from collections import deque

stack = deque()
stack.append('open-project')
stack.append('edit-file')
stack.append('run-tests')

while stack:
    action = stack.pop()
    print(f'撤销操作: {action}')

两种实现的差别不在容器,而在取出元素的位置。把方向约定写清楚很重要:例如规定生产者只能调用 append(),消费者只能调用 popleft(),可以减少队列代码中左右端混用造成的顺序错误。

用 maxlen 控制历史记录的内存占用

创建 deque 时传入 maxlen,可以得到一个有界双端队列。容器达到上限后,再从一端加入新元素,会自动丢弃另一端最旧的元素。这种行为很适合保存最近的日志、搜索词、指标采样或用户操作。

下面是一个保留最近 3 次请求的完整示例:

from collections import deque

recent_requests = deque(maxlen=3)

for request_id in ['req-101', 'req-102', 'req-103', 'req-104']:
    recent_requests.append(request_id)
    print(request_id, '->', list(recent_requests))

print('当前历史:', list(recent_requests))
print('容量上限:', recent_requests.maxlen)

最终历史中只会留下 req-102req-103req-104。调用方不需要在每次写入后手动检查长度并删除旧数据,容量限制直接成为容器的一部分。

不过,自动淘汰也意味着数据会无提示地离开容器。如果历史记录涉及审计、计费或不可丢失的业务事件,就不应只依赖 maxlen;应将持久化存储作为事实来源,把 deque 用作内存中的近期窗口。

采用时检查这几个问题

选择 deque 前,可以根据访问模式做判断:

  • 主要在尾部追加和弹出,而且经常按索引读取时,list 通常更自然。
  • 需要从头部持续取出元素时,使用 deque.popleft() 表达队列语义。
  • 需要后进先出的处理顺序时,组合 append()pop()
  • 只关心最近 N 条数据时,在构造函数中设置 maxlen=N
  • 弹出操作可能遇到空容器时,显式执行空值检查或捕获 IndexError
  • 数据不能丢失时,不要把内存中的 deque 当成持久化消息队列。

deque 的价值不在于替代所有列表,而在于让“两端操作”成为清晰、直接的代码。当队列方向、容量限制和数据丢失边界都被明确下来,它能用很少的代码解决一类常见的容器问题。


相关推荐