在Python中,生成器(Generator)是一种特殊类型的函数,它可以用于迭代(iteration)操作。
生成器函数使用关键字yield
而不是return
来返回值,这使得它能够在每次迭代时产生一个值,并在下一次迭代时继续执行。生成器在每次迭代中只计算并返回一个值,这样可以有效地节省内存空间。
生成器函数的结构类似于普通函数,但其特点是在执行过程中会暂停并保存当前的状态,等待下一次迭代时继续执行。生成器可以通过yield
语句返回一个值,并且可以使用for
循环或者next()
函数来迭代生成器中的值。
创建生成器的方式有两种:
生成器表达式
简单快捷,足以胜任简单的生成器创建工作,其形式和 列表推导式 类似,只不过将中括号 []
替换成了小括号 ()
.
In [1]: generator = (x * x for x in range(8))
In [2]: type(generator)
Out[2]: generator
In [3]: lst = [x * x for x in range(8)]
In [4]: type(lst)
Out[4]: list
生成器函数
要让一个 函数 成为 生成器函数 很简单,只需要将函数中的 return
替换为 yield
。
def fib(num):
n, a, b = 0, 0, 1
while n < num:
yield b
a, b = b, a + 1
n = n + 1
return 'done'
f = fib(6)
print(type(f)) #
生成器函数中,每次执行到 yield
语句就会返回,再次执行时从上次返回的 yield
语句处继续执行。
from collections.abc import Generator
from collections.abc import Iterator
print(issubclass(Generator, Iterator)) # True
print(set(dir(Generator)) - set(dir(Iterator))) # 生成器有三个专属方法 {'throw', 'close', 'send'}
生成器
和迭代器
在 Python 中有密切的关系,可以说生成器是一种特殊类型的迭代器
。
迭代器(Iterator)是一个对象,它实现了迭代协议,即具有__iter__()
和__next__()
方法。迭代器可以使用iter()
函数进行迭代,并使用next()
函数获取下一个元素。迭代器每次返回一个值,并在迭代过程中记录自身的状态。
生成器是一种特殊的迭代器,可以通过生成器函数创建。生成器函数使用yield
语句来产生值,每次调用生成器的next()
方法时,生成器会执行并返回yield
语句产生的值,然后暂停保存状态。这意味着生成器具有迭代器的所有特性,可以被for
循环和next()
函数使用。
迭代器是一种更通用的概念,它可以通过实现迭代协议来创建。而生成器是一种特殊类型的迭代器
,它通过生成器函数的方式来创建,并具有延迟计算和状态保存的特性。因此,可以说生成器是迭代器的一种实现方式,是迭代器的一种特例。
生成器具有以下特点:
next()
方法时从上次暂停的地方继续执行。这使得生成器能够处理需要记住状态或上下文的任务。前面我们已经了解了什么是生成器,以及如何创建生成器。这里我们看下生成器比较容易混淆的两个概念和生成器的运行原理:
def gen(num):
while num > 0:
yield num
num -= 1
return 100
# 使用生成器函数创建生成器对象
g = gen(5)
first = next(g)
# 迭代生成器中的值
for i in g:
print(i)
gen(num)
是生成器函数。g = gen(num)
,调用生成器函数得到生成器对象
,即g
是生成器对象。在给定的代码中,定义了一个生成器函数gen(num)
,它使用yield
语句产生从num
到1的倒序序列。当num
大于0时,通过yield
语句返回num
的值,并在下一次迭代时继续执行。当num
小于等于0时,生成器函数会执行完毕并返回值100
。
下面是代码的执行过程:
g
,并将num
参数设为5。next(g)
,生成器开始执行,进入循环。yield num
语句产生值5
,并将生成器暂停。此时first
变量被赋值为5
。for
循环迭代生成器g
,从下一次迭代开始,即num
为4
。yield num
语句产生当前的num
值,并将生成器暂停。被产生的值依次为4
、3
、2
、1
。num
变为0
时,生成器函数执行完毕,返回值100
。for
循环结束,没有更多的值可迭代。因此,代码的输出结果将是:
4
3
2
1
请注意,return
语句在生成器函数中不会被执行,因为生成器的返回值是由yield
语句产生的值决定的,而不是通过return
语句返回的。在这个例子中,return 100
语句不会影响生成器的输出结果。
在进行若干次之后, 这个时候它会运行这个return, 由于它(return)是在生成器函数里面了, 所以return等价于raise stop iteration。
这个return不管你有没有return值,它都不会在你调用next的时候被返回回去。如果你想拿到这个return value的话,你需要去catch那个stop iteration的exception。
生成器在处理大量数据或者无限序列时非常有用,因为它们可以逐个生成并处理数据,而不需要一次性将所有数据加载到内存中。这种惰性计算的特性使得生成器在处理大型数据集或者需要逐步计算的任务时具有很高的效率。
迭代器版链表遍历
迭代器版链表迭代:
#! -*-conding=: UTF-8 -*-
# 2023/5/22 18:43
class NodeIter:
def __init__(self, node):
self.curr_node = node
def __next__(self):
if self.curr_node is None:
raise StopIteration
node, self.curr_node = self.curr_node, self.curr_node.next
return node
class Node:
def __init__(self, name):
self.name = name
self.next = None
def __iter__(self):
return NodeIter(self)
node1 = Node("node1")
node2 = Node("node2")
node3 = Node("node3")
node1.next = node2
node2.next = node3
if __name__ == '__main__':
for node in node1:
print(node.name)
生成器版链表迭代
#! -*-conding=: UTF-8 -*-
# 2023/5/22 18:43
class Node:
def __init__(self, name):
self.name = name
self.next = None
def __iter__(self):
node = self
while node is not None:
yield node
node = node.next
node1 = Node("node1")
node2 = Node("node2")
node3 = Node("node3")
node1.next = node2
node2.next = node3
if __name__ == '__main__':
for node in node1:
print(node.name)
链表本身的iterator,把这个node变成了一个iterable, 这一次我们也是想把这个node变成iterable。但是这个iter函数我们直接把它做成一个生成器,也就是当你做iter node的时候,它返回的是一个generator object。
某些情况下,生成器实现的链表相比迭代器版是不是清爽简洁多了。
生成器的完整名称应该是生成器对象, 既然是一个对象,那么自然有 方法 和 属性。
g = (x for x in range(10))
help(g)
Help on generator object:
<genexpr> = class generator(object)
| Methods defined here:
|
| __del__(...)
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __iter__(self, /)
| Implement iter(self).
|
| __next__(self, /)
| Implement next(self).
|
| __repr__(self, /)
| Return repr(self).
|
| close(...)
| close() -> raise GeneratorExit inside generator.
|
| send(...)
| send(arg) -> send 'arg' into generator,
| return next yielded value or raise StopIteration.
|
| throw(...)
| throw(typ[,val[,tb]]) -> raise exception in generator,
| return next yielded value or raise StopIteration.
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| gi_code
|
| gi_frame
|
| gi_running
|
| gi_yieldfrom
| object being iterated by yield from, or None
send()
函数是用于与生成器进行交互的一种方法。它允许在生成器的每次暂停(yield)点发送值,并且该值将成为yield
表达式的结果。
下面是关于send()
函数的一些重要事项:
yield
语句:yield
语句是生成器函数中的特殊语句,它用于生成值并将生成器暂停。当生成器执行到yield
语句时,它会产生一个值,并将该值返回给调用方。同时,生成器会保留它的状态,以便在下一次迭代时从暂停的地方继续执行。send()
函数:send()
函数用于向生成器发送值,并恢复生成器的执行。它的语法为generator.send(value)
,其中generator
是生成器对象,value
是要发送给生成器的值。send()
函数会将值发送给生成器,并将该值作为yield
表达式的结果。同时,生成器会从上一次暂停的地方继续执行,并执行到下一个yield
语句或函数结束。需要注意的是,当首次调用生成器时,必须使用next()
函数来启动生成器,或者使用send(None)
,将None
作为参数发送给生成器。这是因为生成器在第一次调用时需要被激活,以准备接收来自send()
函数的值。
send不接收
def gen(num):
while num > 0:
yield num
num -= 1
return 100
# 使用生成器函数创建生成器对象
g = gen(2)
first = next(g) # first =g.send(none)
print(f"first: {first}")
print(f"first: {g.send(5)}")
# 迭代生成器中的值
for i in g:
print(i)
输出结果为:
first: 2
first: 1
yield num
没有被赋值给任何东西,g.send(5)
相当于被扔掉了。
send接收
def gen(num):
while num > 0:
tmp = yield num
if tmp is not None:
num = tmp
num -= 1
return 100
# 使用生成器函数创建生成器对象
g = gen(2)
first = next(g) # first =g.send(none)
print(f"first: {first}")
print(f"send: {g.send(5)}")
# 迭代生成器中的值
for i in g:
print(i)
输出结果为:
first: 2
send: 4
3
2
1
生成器对象的close()
方法用于关闭生成器。调用close()
方法后,生成器会抛出一个GeneratorExit
异常,表示生成器已关闭。
关闭生成器有以下几个效果:
yield
语句时,会抛出GeneratorExit
异常,可以在生成器内部通过捕获该异常来执行一些清理操作。finally
语句块,那么在生成器关闭时,finally
语句块中的代码会被执行。def gen():
try:
for i in range(10):
yield i
except GeneratorExit:
print('close generator')
g = gen()
print(next(g))
g.close()
print(next(g))
输出结果为:
0
close generator
Traceback (most recent call last):
File "F:/study/pythonProjects/ss.py", line 15, in <module>
print(next(g))
StopIteration
生成器提供了一个throw()
方法,允许在生成器内部引发一个指定的异常。
throw()
方法的语法如下:
generator.throw(exception[, traceback])
exception
参数是要引发的异常对象。traceback
参数是可选的,用于指定异常的回溯信息(traceback)。当调用generator.throw()
方法时,生成器会在当前的yield
表达式上引发指定的异常。然后,异常会在生成器的当前位置被捕获,可以通过使用try
/except
语句来处理异常。
下面是一个示例代码,演示了生成器的throw()
方法的使用:
def my_generator():
try:
yield 1
yield 2
yield 3
except ValueError as e:
print(f"Caught exception: {e}")
gen = my_generator()
print(next(gen)) # 输出: 1
print(next(gen)) # 输出: 2
gen.throw(ValueError("Custom exception")) # 引发异常,并在生成器内部捕获
print(next(gen)) # 输出: 3
在上述示例中,my_generator()
函数是一个生成器函数,它会生成数值 1、2 和 3。在生成器中,我们使用了try
/except
语句来捕获ValueError
异常。当调用gen.throw()
方法并传递一个ValueError
异常时,生成器会在当前的yield
表达式上引发该异常,并在生成器内部捕获。
输出结果为:
1
2
Caught exception: Custom exception
3
注意,在生成器捕获异常后,生成器会继续执行后续的yield
表达式。在上述示例中,即使引发了异常,yield 3
语句仍然会被执行,并返回最后一个值 3。
它的实现手段是通过向生成器对象在上次被挂起处,抛出一个异常。之后会继续执行生成器对象中后面的语句,直至遇到下一个yield语句返回。如果在生成器对象方法执行完毕后,依然没有遇到yield语句,抛出StopIteration异常。
生成器是一种特殊的迭代器(注意这个逻辑关系反之不成立)。使用生成器,你可以写出来更加清晰的代码;合理使用生成器,可以降低内存占用、优化程序结构、提高程序速度。
喜欢这篇文章的话,可以关注一下我的公众号『海哥python』