Python 中生成器(Generator)和列表(List)细分

0 阅读1分钟

在 Python 中,生成器(Generator)和列表(List)都是常用的可迭代对象,但它们在底层实现、内存占用、访问方式和使用场景上有本质区别。

核心区别

特性

列表

生成器

存储方式

一次性在内存中创建并存储所有元素

惰性求值,按需生成元素,不保存所有值

内存占用

高(随元素个数线性增长)

低(固定,只保存当前状态)

访问方式

支持索引、切片、重复迭代

只能顺序迭代一次,不支持索引

创建方式

[expr for x in iterable]list()

(expr for x in iterable)yield 函数

迭代次数

可以多次迭代

只能迭代一次,迭代完即停止

速度

生成快(预分配内存),访问快(随机访问)

生成略慢(每次计算),顺序访问快

适用场景

需要多次访问、随机访问、修改元素

处理大数据流、无限序列、节省内存

详细对比

1. 内存占用

  • 列表:将每个元素存储在连续的内存空间中。例如 range(1000000) 转换为列表会占用约 8MB 内存(每个整数 28 字节)。

  • 生成器:只记录当前状态(如当前索引、步长),不保存历史元素。例如 (x for x in range(1000000)) 只占用几十字节内存。

    import sys

    lst = [x for x in range(1000000)] gen = (x for x in range(1000000))

    print(sys.getsizeof(lst)) # 约 8000056 字节 print(sys.getsizeof(gen)) # 约 112 字节(固定)

2. 访问方式

  • 列表:支持索引、切片、len(),可多次迭代。

    gen = (x for x in [1, 2, 3]) print(next(gen)) # 1 print(next(gen)) # 2

    无法使用 gen[0] 或 len(gen)

3. 迭代次数

  • 列表:可以重复迭代任意次数。

    lst = [1, 2, 3] for i in lst: print(i) # 第一次 for i in gen: print(i) # 第二次

  • 生成器:只能迭代一次,迭代完后会自动停止(抛出 StopIteration)。

    gen = (x for x in [1, 2, 3]) for i in gen: print(i) # 第一次,输出 1 2 3 for i in gen: print(i) # 第二次, 无输出(已耗尽)

4. 创建方式

  • 列表:方括号 [] 列表推导式,或 list() 构造函数。

    lst = [x**2 for x in range(5)] # 列表推导式 lst2 = list(range(5)) # 从可迭代对象构造

  • 生成器:圆括号 () 生成器表达式,或 yield 函数。

    gen = (x2 for x in range(5)) # 生成器表达式 def squares(n): for i in range(n): yield i2 # 生成器函数

5. 性能差异

  • 列表:生成时需要预先计算所有元素,如果元素很多或计算复杂,可能耗时且占内存。但生成后访问速度很快(O(1) 索引)。

  • 生成器:生成每个元素的延迟很小,但每次迭代都需要重新计算,整体迭代速度可能略慢(但差别不大)。适合处理流式数据或无限序列。

6. 适用场景

  • 使用列表

    • 需要多次遍历数据

    • 需要随机访问(通过索引)

    • 数据量不大,内存充足

    • 需要修改、追加、删除元素

  • 使用生成器

  • 处理超大文件或数据集,避免内存溢出

  • 生成无限序列(如斐波那契数列)

  • 管道式数据处理(一个生成器的输出作为另一个的输入)

  • 只需一次遍历的场景

示例对比

例1:计算平方和

# 列表方式
def sum_squares_list(n):
    squares = [x**2 for x in range(n)]   # 一次性存储所有平方
    return sum(squares)

# 生成器方式
def sum_squares_gen(n):
    return sum(x**2 for x in range(n))   # 按需生成,不存储

n 很大时,列表方式会占用大量内存,生成器方式几乎没有额外内存开销。

例2:读取大文件

# 列表方式(不推荐)
lines = open('large_file.txt').readlines()  # 将所有行加载到内存

# 生成器方式(推荐)
def read_lines(file):
    with open(file) as f:
        for line in f:
            yield line                     # 每次返回一行

总结

  • 列表是 “一次性拿出所有”,适合小数据量或需要多次访问的场景。

  • 生成器是 “用的时候再产生”,适合大数据量、流式处理、节省内存的场景。

在实际开发中,应根据具体需求选择:内存允许时用列表更方便;处理海量数据或无限序列时用生成器更明智。