python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python迭代器与生成器

Python迭代器与生成器精讲之大幅降低内存占用

作者:二月龙

生成器本质上也是一种迭代器,是一种特殊的迭代器,这篇文章主要介绍了Python迭代器与生成器精讲之大幅降低内存占用的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

前言

处理大规模数据时,内存往往是第一道瓶颈。列表一次要把所有数据加载进内存,100 万条记录可能直接吃掉几百 MB。迭代器和生成器,就是 Python 给你的省内存利器

一、先搞清:什么是可迭代对象

能被 for 循环遍历的,都叫可迭代对象(Iterable)

python
list, tuple, str, dict, set  → 都是可迭代对象

但可迭代 ≠ 迭代器。

二、迭代器(Iterator):边用边取,用完即丢

迭代器是一个记住了当前位置的对象,每次只返回一个值,取完就没了。

python
it = iter([1, 2, 3])
print(next(it))  # 1
print(next(it))  # 2
print(next(it))  # 3
print(next(it))  # StopIteration ← 取完了

关键特性

特性说明
__iter__() 方法返回自己
__next__() 方法返回下一个值,没有则抛 StopIteration
只能前进,不能回退像磁带,不能倒带
只能遍历一次读完就空了

列表是可迭代对象,但不是迭代器:

python
lst = [1, 2, 3]
hasattr(lst, '__next__')  # False

it = iter(lst)
hasattr(it, '__next__')   # True ← 这才是迭代器

for 循环的本质,就是不断调用 next(),直到捕获 StopIteration

三、生成器(Generator):写起来像函数,用起来像迭代器

生成器是一种特殊的迭代器,不用写类,用 yield 就能造出来。

1. 生成器函数

python
def count_up_to(n):
    i = 1
    while i <= n:
        yield i      # 暂停,返回 i,下次从这里继续
        i += 1

gen = count_up_to(5)
print(next(gen))  # 1
print(next(gen))  # 2

yieldreturn 的区别:

returnyield
执行后函数结束,局部变量销毁暂停,局部变量保留
调用次数一次可以多次 resume
返回值一个结果每次返回一个值

2. 生成器表达式

语法和列表推导式一样,只是把 [] 换成 ()

python
# 列表推导式 → 一次性生成所有数据,占内存
squares_list = [x**2 for x in range(1000000)]   # ~40 MB

# 生成器表达式 → 用一个取一个,几乎不占内存
squares_gen = (x**2 for x in range(1000000))    # ~几十字节

四、内存对比:数据说话

处理 1000 万个整数的平方:

方式内存占用说明
列表 [x**2 for x in range(10_000_000)]~80 MB全部加载进内存
生成器 (x**2 for x in range(10_000_000))~120 字节只存当前状态
迭代器 iter(range(10_000_000))~48 字节连计算都省了

差距是 6 位数级别的。

五、实战:什么时候该用生成器

适合用生成器的场景

场景原因
读取大文件(GB 级)不用一次性读入内存
数据管道/流处理上游产一个,下游消一个
无限序列列表存不下,生成器可以无限产生
中间结果不需要保留用完就丢,没必要存

示例:逐行读取大文件

python
# ❌ 错误:一次性读入内存
with open('huge_log.txt') as f:
    lines = f.readlines()  # 内存爆炸

# ✅ 正确:生成器逐行读取
with open('huge_log.txt') as f:
    for line in f:         # f 本身就是迭代器
        if 'ERROR' in line:
            print(line)

open() 返回的文件对象本身就是迭代器,每次 for 循环只读一行。

示例:数据管道

python
def read_log(filename):
    with open(filename) as f:
        for line in f:
            yield line.strip()

def filter_errors(lines):
    for line in lines:
        if 'ERROR' in line:
            yield line

def extract_time(lines):
    for line in lines:
        yield line.split()[0]

# 管道组合:数据从左到右流动,全程不存中间结果
pipeline = extract_time(filter_errors(read_log('app.log')))
for t in pipeline:
    print(t)

三个生成器串联,内存占用始终只有一行数据的大小

六、yield from:生成器嵌套的简洁写法

python
def gen1():
    yield 1
    yield 2

def gen2():
    yield from gen1()  # 等价于逐个 yield gen1() 的值
    yield 3

print(list(gen2()))  # [1, 2, 3]

yield from 还能透明传递 send()throw(),是生成器组合的推荐写法。

七、常见误区

误区真相
生成器比列表快不一定。生成器省内存,但有 yield 开销。小数据用列表更快
生成器可以倒回去不能。要重遍历,重新调用函数
yield 后的代码不执行执行,只是暂停。下次 next()yield 下一行继续
生成器表达式可以重复用不能。用完就空了,要重新创建

八、一张表总结

概念本质内存复用性典型写法
列表一次性存储所有元素✅ 可重复[x for x in range(n)]
迭代器边用边取,记住位置极低❌ 一次性iter(list)
生成器yield 实现的迭代器极低❌ 一次性(x for x in range(n))
生成器函数包含 yield 的函数极低❌ 一次性def f(): yield x

核心结论

省下来的内存,就是你程序能处理的数据量上限。

总结

到此这篇关于Python迭代器与生成器精讲之大幅降低内存占用的文章就介绍到这了,更多相关Python迭代器与生成器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文