一、deque
deque(双端队列) 是 Python 标准库中的一个数据结构。
from collections import deque
previous_lines = deque(maxlen=history)deque(双端队列) 是 Python 标准库中的一个数据结构。maxlen=history是关键参数。它设置了队列的最大长度。当你往队列里添加新元素时,如果队列已满(达到maxlen),它会自动把最老的那个元素挤出去(FIFO,先进先出)。
具体使用方法
deque(发音类似 "deck")是 Python 标准库 collections 模块中的一个双端队列数据结构。
它的最大特点是:在两端添加或删除元素的速度非常快 ,比普通列表 list 快得多。
1. 导入与基本语法
导入:
因为是标准库里的模块,使用前需要先导入:
python
from collections import deque
创建对象:
python
# 创建一个空的双端队列
d = deque()
# 创建一个包含数据的队列
d = deque([1, 2, 3])
# 创建一个固定长度的队列(重点!)
d = deque(maxlen=5)
2. 核心方法详解
deque 提供了在两端操作数据的方法,记住 append 是加,pop 是删。
A. 右端操作(和 list 一样)
d.append(x):在右端添加元素 x。d.pop():移除并返回右端的元素。
B. 左端操作(deque 的强项,list 做这个很慢)
d.appendleft(x):在左端添加元素 x。d.popleft():移除并返回左端的元素。
C. 扩展操作
d.extend(iterable):在右端扩展多个元素。d.extendleft(iterable):在左端扩展多个元素。
3. 代码示例:具体怎么用
场景一:两端操作对比 list
这是 deque 最基础的优势。如果你要在列表头部频繁插入数据,用 list 效率极低,用 deque 是瞬间完成。
python
from collections import deque
d = deque([1, 2, 3])
# 右端操作
d.append(4) # deque([1, 2, 3, 4])
print("append后:", d)
# 左端操作
d.appendleft(0) # deque([0, 1, 2, 3, 4])
print("appendleft后:", d)
# 左端删除
x = d.popleft() # x = 0, d 变成 deque([1, 2, 3, 4])
print("popleft取出了:", x)
场景二:固定长度窗口(maxlen 参数)
这是你最初代码中用到的高级用法。设置了 maxlen 后,队列就像一个固定大小的管子,新数据进去,老数据就会被自动挤出来。
python
from collections import deque
# 创建一个最大长度为 3 的队列
d = deque(maxlen=3)
d.append(1)
d.append(2)
d.append(3)
print(d) # 输出: deque([1, 2, 3], maxlen=3) -> 满了
# 再加一个数据 4
d.append(4)
# 因为长度限制,最左边的 1 被自动挤走了
print(d) # 输出: deque([2, 3, 4], maxlen=3)
场景三:移动窗口(类似日志滚动)
这就完美对应了你最开始贴出的代码逻辑:保留最近 N 条记录。
python
def tail(filename, n=5):
"""模拟 tail -n 命令,返回文件的最后 n 行"""
return deque(open(filename), maxlen=n)
# 对于你的代码逻辑:
# previous_lines = deque(maxlen=history)
# 每次循环 append 进去,如果超过了 history,最老的那一行自动消失,不用你手动写代码去删除。
4. 性能对比:为什么要用 deque?
这是面试或深入理解时常问的点。
- list (列表) :底层是数组。如果在头部插入数据(
insert(0, x)),后面的所有元素都要在内存里向后移动一位。数据量大时,非常慢。 - deque :底层是双向链表。在头部或尾部插入数据,只需要改一下指针指向,不管数据量多大,速度都是一样的快(O(1) 复杂度)。
实验代码(你可以跑一下感受差距):
python
import time
from collections import deque
# 测试在头部插入 100,000 个数据
# 1. 使用普通 list
start = time.time()
lst = []
for i in range(100000):
lst.insert(0, i) # 在头部插入
print(f"List 耗时: {time.time() - start:.4f} 秒")
# 2. 使用 deque
start = time.time()
dq = deque()
for i in range(100000):
dq.appendleft(i) # 在头部插入
print(f"Deque 耗时: {time.time() - start:.4f} 秒")
结果预测 :list 会明显卡顿(可能几秒),deque 几乎是瞬间完成(0.0x 秒)。
总结
- 普通列表
list:适合随机访问、在尾部操作。 - 双端队列
deque:- 适合频繁在头部 或尾部操作(如队列、栈)。
maxlen参数是实现"保留最近N条记录"的神器,自动过期老数据。
二、yield+next
yield 是 Python 中定义生成器 的关键字。
简单来说,yield 是一个"懒惰"的 return。
return:返回结果,函数结束,销毁局部变量。yield:返回结果,函数暂停(休眠),保存当前状态(变量值、代码运行到哪一行),等待下次被唤醒继续执行。
1. 基本语法结构
python
def my_generator():
# 代码块 1
yield 值A
# 代码块 2
yield 值B
# 代码块 3
return 结束 # (可选,生成器结束时会自动抛出 StopIteration)
核心规则:
- 只要函数内部包含
yield,调用这个函数时,代码不会立即执行,而是返回一个生成器对象。 - 只有当你去迭代这个对象==(如
for循环或next())==时,函数体才开始执行。 - 遇到
yield,函数暂停并返回后面的值。 - 下次迭代时,从上次暂停的地方继续往下跑。
2. 具体使用场景与示例
场景一:像流水线一样逐个产出(手动控制)
这是理解 yield 最好的方式。我们对比一下"一次性做完"和"分步做完"。
python
def count_down(n):
print("开始倒计时...")
while n > 0:
yield n # 暂停,把 n 交出去
n -= 1
print("倒计时结束!")
# 1. 创建生成器(此时并没有打印"开始倒计时")
gen = count_down(3)
print(f"生成器对象: {gen}")
# 2. 手动获取第一个值
print(f"第一次: {next(gen)}")
# 执行流程:开始运行函数 -> 打印"开始倒计时" -> 遇到 yield 3 -> 暂停,返回 3
# 输出: 第一次: 3
# 3. 手动获取第二个值
print(f"第二次: {next(gen)}")
# 执行流程:从上次暂停处醒来 -> n 减 1 变成 2 -> 遇到 yield 2 -> 暂停,返回 2
# 输出: 第二次: 2
# 4. 手动获取第三个值
print(f"第三次: {next(gen)}")
# 输出: 第三次: 1
场景二:处理海量数据(节省内存)
这是 yield 最实用的场景。假设你要处理 1 亿条数据,如果用 return 返回列表,内存直接爆满;用 yield 则只占用一条数据的内存。
用 return(内存杀手):
python
def get_all_numbers(n):
nums = []
for i in range(n):
nums.append(i)
return nums # 必须等 1亿个数都存好,内存爆炸
# 调用
res = get_all_numbers(100000000)
用 yield(内存友好):
python
def get_numbers_stream(n):
for i in range(n):
yield i # 生成一个,给出去一个,内存里永远只有一个 i
# 调用
for num in get_numbers_stream(100000000):
print(num) # 每次只处理一个数,内存几乎不占用
场景三:保留上下文(你的原始代码)
在你的文件搜索代码中,yield 的作用是保留现场。
python
def search(lines, pattern, history=5):
previous_lines = deque(maxlen=history)
for li in lines:
if pattern in li:
# 此时 yield 暂停了函数,把结果给外部处理
# 关键是:previous_lines 这个变量被保留在内存里了!
# 等外部处理完,函数"醒来"继续往下读下一行
yield li, previous_lines
previous_lines.append(li)
如果这里用 return,函数一结束,previous_lines 就销毁了,你就无法维护"最近5行"这个滑动窗口了。
3. 进阶用法:yield 也可以接收数据
除了产出数据,yield 还能接收外部传进来的数据(使用 .send() 方法),这可以让生成器变成"协程"。
python
def accumulator():
total = 0
while True:
# x 接收外部 send 进来的值
x = yield total
if x is None: break
total += x
gen = accumulator()
print(next(gen)) # 启动生成器,输出: 0 (运行到 yield total 暂停)
print(gen.send(10)) # 发送 10 进去,yield 接收赋值给 x,total 变成 10,再次 yield,输出: 10
print(gen.send(20)) # 发送 20 进去,total 变成 30,输出: 30
(注:这属于高级用法,初学者了解即可)
总结
- 语法:写在函数里,后面跟要返回的值。
- 机制 :遇到
yield就暂停,下次调用继续。 - 怎么用 :
- 配合
for循环使用(自动迭代)。 - 配合
next()函数使用(手动获取)。
- 配合
- 核心价值 :节省内存。将"生成所有数据"变成"按需生成数据"。