编程学习网 > 编程语言 > Python > Python 列表生成式与生成器:从立即计算到按需生成
2026
08-17

Python 列表生成式与生成器:从立即计算到按需生成


列表生成式和生成器表达式可以使用几乎相同的推导语法。不同的是,生成式使用方括号,会立即创建完整列表;生成器使用圆括号,返回一个生成器对象。这个看似很小的变化,会进一步影响计算时机、内存占用和数据能否重复使用。

squares_list = [x * x for x in range(5)]
squares_generator = (x * x for x in range(5))
print(squares_list)
print(squares_generator)

第一行直接得到 [0, 1, 4, 9, 16],第二行只创建了生成器,打印时看到的是类似 <generator object <genexpr> at ...> 的对象信息。

同一段推导,两种计算方式

列表生成式与生成器表达式都可以描述从一组输入中逐个取值,经过计算后产生结果。区别在于结果如何保存。

生成式方括号:立即创建完整列表

执行列表生成式时,Python 会遍历输入,把每次计算得到的值放进一个新列表。表达式结束后,所有元素都已经在列表中:

squares = [x * x for x in range(5)]
print(len(squares))
print(squares[2])
print(list(squares))

列表支持 len()、数字索引和重复遍历。这里的代价也很直接:无论后面实际使用多少个元素,创建时都会先把完整结果算出来并保存在内存中。

生成器圆括号:返回生成器对象

把最外层方括号换成圆括号,得到的是生成器表达式:

squares = (x * x for x in range(5))
print(next(squares))
print(next(squares))

两次 next() 依次得到  1。生成器没有提前保存后面的全部平方数,而是在请求下一个值时继续计算。

对比项

列表生成式

生成器表达式

外层语法

[]

()

创建结果

list

生成器迭代器

计算方式

立即产生完整结果

按需产生下一个值

数字索引、len()

支持

不支持

重复遍历

支持

耗尽后不能重新开始

两种写法没有统一的优先级。结果较小,而且后面需要索引、求长度或重复遍历时,列表更方便;数据量较大、可能提前停止,或者只需要把结果交给下一个处理步骤时,生成器更合适。

列表生成式:把转换和筛选写进表达式

列表生成式的基本结构,可以从普通循环一步步对应出来。

基本结构:表达式、for 与输入序列

先用循环生成 1 5 的平方:

squares = []
for x in range(1, 6):
    squares.append(x * x)

循环里的三个角色很明确:range(1, 6) 提供输入,for x 逐个取值,x * x 决定输出。把它们放进列表生成式,顺序变成输出表达式在前,循环在后

squares = [x * x for x in range(1, 6)]

两段代码都会得到 [1, 4, 9, 16, 25]。列表生成式适合表达简单、没有副作用的转换;但是如果一行代码已经需要多个括号来控制逻辑,表达式很复杂,其实更适合使用普通循环来创建列表。

for 后面的 if:过滤元素

 for 后面添加 if,可以决定当前输入是否进入结果列表:

even_squares = [
    x * x
    for x in range(1, 11)
    if x % 2 == 0
]
print(even_squares)

输出为 [4, 16, 36, 64, 100]。执行时先从 range() 取得 x,再检查 x % 2 == 0;条件成立才计算并收集前面的 x * x

这个位置的 if 是过滤条件,因此不能在后面直接添加 else

[x for x in range(1, 6) if x % 2 == 0 else 0]

这段代码会抛出 SyntaxError。过滤只回答当前元素要不要保留,被排除的元素不会再产生替代值。

for 前面的 if...else:决定输出值

如果每个输入都要保留,只是根据条件产生不同结果,就要把条件表达式放到 for 前面:

numbers = [
    x if x % 2 == 0 else -x
    for x in range(1, 6)
]
print(numbers)

输出为 [-1, 2, -3, 4, -5]。这里的 x if ... else -x 必须为每个 计算出一个值,所以 else 不能省略。

两种写法可以用一句执行顺序区分:for 后面的 if 负责筛选输入,for 前面的 if...else 负责计算输出。

多层循环:按照嵌套顺序组合数据

列表生成式可以继续追加 for 子句,其顺序与普通嵌套循环一致:

combinations = [
    left + right
    for left in "ABC"
    for right in "XY"
]
print(combinations)

输出为 ['AX', 'AY', 'BX', 'BY', 'CX', 'CY']。这里得到的是两个集合的笛卡尔积:外层先固定一个 left,内层再遍历所有 right

循环变量也可以直接解包。例如,遍历字典的 items() 时,每一项都是一个键值对:

scores = {"Alice": 95, "Bob": 88}
items = [f"{name}={score}" for name, score in scores.items()]
print(items)

Python 3.7 及之后,字典保持插入顺序,因此这里输出 ['Alice=95', 'Bob=88']

生成器表达式:需要一个再计算一个

列表生成式负责描述转换规则,生成器表达式在此基础上改变了结果的产生方式。它返回的是生成器迭代器,可以被 next()  for 驱动。

[] 换成 ()

下面两段代码使用相同的转换规则:

numbers = range(1, 6)
squares_list = [x * x for x in numbers]
squares_generator = (x * x for x in numbers)

squares_list 创建时已经完成遍历;squares_generator 则等到外部请求数据时,才计算下一个 x * x

惰性计算也有一个细节:生成器表达式最左侧 for 后面的可迭代对象会在创建生成器时求值,其他表达式通常等到取值时再执行。因此,它不是把整行代码无条件推迟到第一次 next(),而是把逐项计算的主要工作延后。

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

total = sum(x * x for x in range(1, 6))
print(total)

sum() 会逐个消费生成器产生的值,最后得到 55,中间不需要先创建平方数列表。

nextfor StopIteration

next() 展示了生成器最直接的消费方式:

squares = (x * x for x in range(3))
print(next(squares))
print(next(squares))
print(next(squares))
print(next(squares))

前三次分别得到 014。第四次已经没有数据可取,Python 会抛出 StopIteration

完整遍历时通常交给 for

squares = (x * x for x in range(3))
for value in squares:
    print(value)

for 会在内部不断请求下一个值,并在遇到 StopIteration 时正常结束循环。代码无需手动捕获这个异常,但 next() 仍然适合只取一个值或手动控制消费节奏的情况。

一次性消费与耗尽状态

生成器保存的是当前执行状态,不是一份可以反复读取的结果集合。走到结尾以后,它会一直保持耗尽状态:

squares = (x * x for x in range(3))
print(list(squares))
print(list(squares))

第一次输出 [0, 1, 4],同时把生成器消费完;第二次只能得到空列表 []。如果需要再次遍历,就要重新创建生成器;如果需要随机访问、求长度或者保留结果,则应当使用列表或主动执行 list(generator)

生成器函数:yield 保存执行现场

生成器表达式适合简单推导。计算过程包含循环状态、多个分支或递推关系时,可以把它写成包含 yield 的生成器函数。

调用函数:先得到生成器对象

下面的函数在三个位置打印日志,并产生两个值:

def trace_values():
    print("start")
    yield 1
    print("resume")
    yield 3
    print("end")

values = trace_values()
print("created")

执行这段代码只会打印 created。调用 trace_values() 创建了生成器对象,函数体还没有开始运行,所以 start 暂时不会出现。

第一次 next:运行到 yield

继续对同一个对象调用 next()

print(next(values))

这时函数从开头执行,先打印 start,再执行 yield 1yield 把数值 交给调用方,同时暂停函数;局部变量、指令位置和当前执行状态都会被保留下来。

这里没有像普通 return 那样结束整个函数。生成器只是暂时把控制权交回调用方,等待下一次请求。

再次 next:从暂停位置继续

第二次调用 next(values) 时,执行会从上一个 yield 后面恢复:

print(next(values))

程序先打印 resume,然后通过 yield 3 产生第二个值并再次暂停。第三次调用时只剩下 print("end"),函数执行到末尾,随后用 StopIteration 表示生成器已经结束。

这套运行到 yield保存状态从原位置恢复的过程,使生成器函数可以自然地表达递推计算:

def fibonacci(count):
    index, current, next_value = 0, 0, 1
    while index < count:
        yield next_value
        current, next_value = next_value, current + next_value
        index += 1

每次需要新值时,函数利用保留下来的 currentnext_value  index 继续计算,无需提前保存整个数列。

return:结束生成器

生成器函数执行到末尾,或者遇到 return,都会结束迭代。如果写成 return "done",这个值会放在 StopIteration.value 中;普通 for 循环会自动处理 StopIteration,不会把 "done" 当成一个普通元素输出。

还有一个容易忽略的地方:每次调用生成器函数都会创建新对象。下面两行都从第一个 yield 开始,因为它们操作的是两个不同的生成器:

print(next(trace_values()))
print(next(trace_values()))

如果希望继续前一次的执行位置,需要先保存对象,再反复对同一个对象调用 next() 或使用 for 遍历。

容易写错的三个地方

列表生成式和生成器的常见错误,都可以回到它们各自的执行位置来判断:

• for 后面的 if 是过滤条件,不能直接带 else;要为每个输入选择输出值,应把完整的 if...else 放到 for 前面。

• next(generator_function()) 每次都会创建新生成器,无法沿用上一次暂停位置;需要先把对象保存到变量中。

生成器是一次性迭代对象,消费完成后不会自动复位;需要重复遍历时,应重新创建生成器或保存为列表。

这三种错误表面上分别属于语法、函数调用和遍历,背后对应的是三个不同问题:当前代码是在筛选输入、创建对象,还是消费已有对象。

[]() yield 怎么选

三种形式可以按照结果规模和使用方式来选择:

形式

产生的对象

适合的任务

[expression for ...]

完整 list

结果较小,需要索引、长度或重复使用

(expression for ...)

生成器迭代器

简单推导,按需消费,可能提前结束

包含 yield 的函数

生成器迭代器

多步计算、递推、分支或需要保存局部状态

列表生成式和生成器表达式都应该保持简洁。当转换规则开始承担复杂控制流时,把逻辑放回普通循环或生成器函数,通常比继续压缩语法更容易维护。

 以上就是“Python 列表生成式与生成器:从立即计算到按需生成的详细内容,想要了解更多Python教程欢迎持续关注编程学习网。

扫码二维码 获取免费视频学习资料

Python编程学习

查 看2022高级编程视频教程免费获取