找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

5149

积分

0

好友

728

主题
发表于 1 小时前 | 查看: 3| 回复: 0

生成器表达式是 Python 中按需产生数据的一种轻量语法。你可以把遍历、筛选和转换都写在一个表达式里,却不用一次性创建并保存所有结果——数据是在迭代过程中逐个生成的。

它的结构和列表推导式很相似,但使用的是圆括号 (),返回的是生成器对象。当你只需要遍历一次、数据规模较大,或者希望在处理过程中提前结束时,生成器表达式再合适不过了。

一、什么是生成器表达式

1、基本语法

生成器表达式使用圆括号,基本形式是:

(输出表达式 for 变量 in 可迭代对象)

比如,我们要产生 0 到 4 的平方:

squares = (
    x ** 2
    for x in range(5)
)

print(squares)

你会看到类似这样的输出:

<generator object <genexpr> at 0x...>

它并没有直接输出平方数值,而是给了一个生成器对象。

这段代码可以拆成三部分:

x ** 2          # 计算当前要产生的元素
for x           # 将当前元素依次绑定给 x
in range(5)     # 指定要遍历的数据

如果想手动控制迭代,可以用 next() 一个个获取元素:

print(next(squares))
print(next(squares))
print(next(squares))

输出:

0
1
4

生成器表达式可以看作简化版的生成器函数。上面的代码与下面这段函数的效果差不多:

def generate_squares():
    for x in range(5):
        yield x ** 2

squares = generate_squares()

生成器函数通过 yield 逐个产出结果,而生成器表达式就是把这种简单逻辑写在一个表达式里。

2、生成器表达式的结果特点

(1)结果是生成器对象

生成器表达式不会变成列表、元组或集合,它返回的就是一个生成器对象:

values = (x ** 2 for x in range(5))

print(type(values))

输出:

<class 'generator'>

所以,像下面这样的写法,别误以为是元组推导式:

values = (x ** 2 for x in range(5))

Python 压根就没有专门的元组推导式。想要元组的话,可以用 tuple() 把生成器表达式包起来:

values = tuple(x ** 2 for x in range(5))

print(values)

输出:

(0, 1, 4, 9, 16)

(2)元素按需产生

生成器表达式采用惰性计算:创建生成器时,通常不会立刻计算所有元素;只有当你请求下一个元素时,对应的计算才会真正执行。

看个例子就明白了:

def double(value):
    print(f"处理 {value}")
    return value * 2

values = (
    double(x)
    for x in range(3)
)

print("生成器已创建")
print(next(values))
print(next(values))

输出:

生成器已创建
处理 0
0
处理 1
2

生成器创建的时候,double() 根本没被调用。只有每次 next() 的时候,才会运行一次计算。

(3)生成器通常只能遍历一次

生成器会记住当前迭代位置,取出来的元素就溜走了,不会自动归位:

values = (x ** 2 for x in range(5))

print(list(values))
print(list(values))

输出:

[0, 1, 4, 9, 16]
[]

第一次 list() 就取光了所有元素,第二次再遍历,生成器已经空了。

如果生成器耗尽后你还继续调用 next(),就会触发 StopIteration

values = (x for x in range(1))

print(next(values))
print(next(values))  # 这一次会抛 StopIteration

第一次 next() 得到 0,再调用就引发异常。不过用 for 循环遍历时,for 会把 StopIteration 当成结束信号,自动结束循环,程序并不会因为这个异常中断。

(4)不支持索引和长度查询

生成器不一次性存着全部元素,所以没办法用索引访问:

values = (x ** 2 for x in range(5))

print(values[0])   # TypeError

len() 也不行:

print(len(values)) # TypeError

如果你需要反复遍历、按索引取值或者提前知道元素数量,列表才是更合适的选择。

3、作为函数参数

生成器表达式经常直接作为函数参数,尤其在聚合计算时非常方便。

比如,求 0 到 4 的平方和:

total = sum(
    x ** 2
    for x in range(5)
)

print(total)  # 30

当生成器表达式是函数调用里唯一的参数时,它自己的圆括号可以省略:

total = sum(x ** 2 for x in range(5))

这里的括号属于 sum() 函数调用,并不会把生成器表达式变成元组。

下面这两种写法完全等价:

total = sum(x ** 2 for x in range(5))
total = sum((x ** 2 for x in range(5)))

第一种更简洁,也最常用。

如果函数还接收别的参数,生成器表达式最好加上自己的括号,比如:

values = sorted(
    (x ** 2 for x in range(5)),
    reverse=True
)

print(values)  # [16, 9, 4, 1, 0]

sum()max()min()any()all() 这些函数都很喜欢和生成器表达式搭档,用来做聚合计算或条件判断。你也可以用 list()tuple() 把生成器里的数据一次性展开成新的容器对象。

二、带条件的生成器表达式

生成器表达式里的条件有两种常见用途:条件筛选和条件表达式。if 放的位置不一样,作用也不同。

1、条件筛选

for 后面加上 if,可以只处理符合条件的元素。基本语法长这样:

(输出表达式 for 变量 in 可迭代对象 if 条件)

比如,只生成偶数的平方:

even_squares = (
    x ** 2
    for x in range(10)
    if x % 2 == 0
)

for value in even_squares:
    print(value)

输出:

0
4
16
36
64

只有被 2 整除的数字才会参与结果生成。

2、条件表达式

条件表达式可以放在输出表达式的位置,根据条件选择不同的结果。语法如下:

(结果1 if 条件 else 结果2 for 变量 in 可迭代对象)

比如,按需生成奇偶标签:

labels = (
    "even" if x % 2 == 0 else "odd"
    for x in range(5)
)

print(list(labels))

输出:

['even', 'odd', 'even', 'odd', 'even']

你会发现,生成器表达式不会像集合推导式那样去重,而是老老实实按遍历顺序一个一个产出结果。

三、包含多个 for 子句的生成器表达式

生成器表达式里可以连续写多个 for 子句,表示多层循环。基本语法:

(输出表达式 for 变量1 in 可迭代对象1 for 变量2 in 可迭代对象2)

多个 for 从左到右对应嵌套循环由外到内的顺序。

举个例子,按需生成二维坐标点:

points = (
    (x, y)
    for x in range(2)
    for y in range(3)
)

for point in points:
    print(point)

输出:

(0, 0)
(0, 1)
(0, 2)
(1, 0)
(1, 1)
(1, 2)

这里 for x 是外层循环,for y 是内层循环,(x, y) 是当前产出的元素。

多层 for 特别适合展平嵌套数据。比如把二维列表压平:

matrix = [
    [1, 2, 3],
    [4, 5, 6]
]

values = (
    value
    for row in matrix
    for value in row
)

print(list(values))  # [1, 2, 3, 4, 5, 6]

只有在你调用 list() 时,它才真正把所有数据推出来;如果用 for 循环,就会逐个处理,不会一下子全挤进内存。

四、生成器表达式的典型应用场景

生成器表达式天生适合那些“只跑一趟”的数据处理任务。它常常与聚合函数、短路判断函数、文件对象或其他生成器一起协作。

1、直接进行聚合计算

sum()max()min() 这类函数可以直接吃进生成器,不用先造一座列表的“桥梁”。

例如,计算 0 到 999999 的平方和:

total = sum(
    x ** 2
    for x in range(1_000_000)
)

print(total)

因为平方值是一个一个产生的,所以根本不用先存一百万个结果到列表里。

2、使用 any() 和 all() 提前结束判断

any() 在碰到第一个真值时就会收工,all() 在碰到第一个假值时也会停下。搭配生成器表达式,后面没必要的元素根本不会被检查。

比如,检查一组 AI 预测置信度中是否有低于 0.6 的:

confidences = [0.96, 0.91, 0.54, 0.88, 0.93]

has_low_confidence = any(
    confidence < 0.6
    for confidence in confidences
)

print(has_low_confidence)  # True

any() 发现 0.54 < 0.6 之后,就能判定结果为 True,后面的数值不需要再管了。

类似地,想确认所有预测是否都达到最低要求,可以这样写:

all_confident = all(
    confidence >= 0.5
    for confidence in confidences
)

print(all_confident)  # True

3、逐行处理文件数据

文件对象本身就是可迭代的,一行行吐给你。和生成器表达式配合,不用把整个文件读到内存就能边读边转、边筛。

假设 scores.txt 里每行记录一个分数:

with open("scores.txt", encoding="utf-8") as file:
    scores = (
        float(line.strip())
        for line in file
        if line.strip()
    )

    highest_score = max(scores)

print(highest_score)

这里,max() 请求数据时,生成器才去逐行读取、去空白、转浮点数。

⚠️ 注意:因为生成器依赖打开的文件,所以它的消费过程必须放在 with 代码块内完成。

4、构建惰性处理管道

多个生成器表达式可以串成一个流水线,把复杂处理拆成多个清晰的步骤。

numbers = range(20)

even_numbers = (
    number
    for number in numbers
    if number % 2 == 0
)

squares = (
    number ** 2
    for number in even_numbers
)

large_squares = (
    value
    for value in squares
    if value > 50
)

print(list(large_squares))

输出:

[64, 100, 144, 196, 256, 324]

这里三个生成器分别负责筛选偶数、计算平方、挑选大于 50 的结果。每个阶段都按需从上个阶段取数据,整个流程不会提前生成一堆中间列表。

五、常见问题及使用建议

1、不要重复使用已经耗尽的生成器

生成器一般只能完整遍历一次:

values = (x ** 2 for x in range(5))

print(sum(values))  # 30
print(sum(values))  # 0,生成器已空

如果确实需要重复遍历,要么重新创建一个生成器:

values1 = (x ** 2 for x in range(5))
values2 = (x ** 2 for x in range(5))

要么在数据量合理的情况下,干脆用列表。

2、需要索引或重复遍历时使用列表推导式

生成器表达式很适合逐个处理,但不擅长这些事:

  • 通过索引访问元素
  • 多次遍历同一批结果
  • 使用切片
  • 直接查询元素数量
  • 长期保存全部结果

这些场景,列表推导式更对路:

values = [
    x ** 2
    for x in range(5)
]

该选生成器还是列表,得看后续怎么用,不能只看写法是否简短。

3、转换为容器后就不再节省内存

像下面这样,虽然用了生成器表达式,可 list() 最后还是会保存全部结果:

values = list(x ** 2 for x in range(1_000_000))

这并不会减少最终列表占用的内存。如果目的就是为了生成列表,直接写列表推导式反而更明白:

values = [
    x ** 2
    for x in range(1_000_000)
]

生成器表达式的空间优势,只在元素被逐个消费、不需要完整保存时才体现得出来。

4、注意错误可能在迭代时才出现

因为生成器是惰性的,创建的时候未必立刻执行输出表达式里的操作。

values = (
    10 / x
    for x in [2, 1, 0, 5]
)

这段代码一般不会立马报错。只有当你真去迭代到 x = 0 时,才会抛出 ZeroDivisionError

for value in values:
    print(value)

所以,生成器里的坑,往往是在消费数据时才暴露的。

5、避免副作用操作

生成器表达式的本职是按需产生数据,别让它去干打印、写文件、修改外部状态这类事儿。如果主要目的是执行副作用,老老实实用普通 for 循环。

📘 小结

生成器表达式可以一边遍历一边完成转换和筛选,按需一个一个产出元素。它特别适合聚合计算、短路判断、文件流处理和惰性数据管道。使用时牢记:生成器通常只能遍历一次,不支持索引和长度查询;只有当结果被逐个消费时,它的空间优势才能发挥出来。




上一篇:累计亏损23亿元,法国乐天电商平台宣布年底关停
下一篇:IDC发布:二季度国内手机出货下滑4.3%,华为苹果逆势大涨
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-7-24 08:15 , Processed in 1.083599 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表