写法上的微小区别

[x ** 2 for x in range(10)]      # 列表推导式:返回 list
(x ** 2 for x in range(10))      # 生成器表达式:返回生成器

只有方括号 vs 圆括号的区别,但内部机制天差地别。

内存对比

import sys

lst = [x for x in range(10_000_000)]
gen = (x for x in range(10_000_000))

print(sys.getsizeof(lst))    # ~80 MB
print(sys.getsizeof(gen))    # ~200 字节!

生成器几乎不占内存——它用完一个再生成一个

速度对比

import time

# 求和 10M 个平方
nums = range(10_000_000)

t0 = time.time()
sum([x ** 2 for x in nums])
print(time.time() - t0)    # ~1.0s(含建大列表的开销)

t0 = time.time()
sum(x ** 2 for x in nums)
print(time.time() - t0)    # ~0.7s

当下游只是迭代一次的时候,生成器更快——少了"建列表"这一步。

怎么选

场景 用谁
数据要重复访问 / 切片 / 索引 列表
只迭代一次 生成器
数据量很大或无限 生成器(必须)
数据量小 + 后面要 print 看 列表(生成器只能看一次)
函数参数 生成器:sum(x*2 for x in nums)

函数参数的"括号省略"

很多函数允许省略生成器表达式的外层括号:

sum(x ** 2 for x in range(10))         # OK
max(len(s) for s in words)             # OK
" ".join(s.upper() for s in words)     # OK

sorted(x ** 2 for x in nums)           # OK

生成器是一次性的

这是它最大的"陷阱":

gen = (x for x in [1, 2, 3])
list(gen)    # [1, 2, 3]
list(gen)    # []  —— 已经空了!

要重复访问,要么用列表,要么把生成器包进函数每次调用:

def make_gen():
    return (x for x in [1, 2, 3])

list(make_gen())    # [1, 2, 3]
list(make_gen())    # [1, 2, 3]

实战:流式读 + 流式处理

def read_large_csv(path):
    with open(path, encoding="utf-8") as f:
        next(f)    # 跳过表头
        for line in f:
            yield line.strip().split(",")

def filter_by_date(rows, year):
    for row in rows:
        if row[0].startswith(year):
            yield row

def to_floats(rows):
    for row in rows:
        yield [float(x) for x in row[1:]]

# 三层管道,全程不存中间列表
total = sum(
    sum(values)
    for values in to_floats(filter_by_date(read_large_csv("big.csv"), "2026"))
)

这种"管道式"处理在数据工作中超级常见。

下一篇讲上下文管理器——with 关键字背后的机制。