生成器
引入
在Python
中,使用生成器可以很方便的支持迭代器协议。生成器通过生成器函数产生,生成器函数可以通过常规的def
语句来定义,但是不用return
返回,而是用yield
一次返回一个结果,在每个结果之间挂起和继续它们的状态,来自动实现迭代协议。
也就是说,yield
是一个语法糖,内部实现支持了迭代器协议,同时yield
内部是一个状态机,维护着挂起和继续的状态。
def my_range(n):
i = 0
while i < n:
yield i
i += 1
my_range = my_range(3)
print(my_range)
print(next(my_range))
# print([i for i in my_range])
在这个例子中,定义了一个生成器函数,函数返回一个生成器对象,然后就可以通过for
语句进行迭代访问了。
其实,生成器函数返回生成器的迭代器。 "生成器的迭代器"这个术语通常被称作"生成器"。要注意的是生成器就是一类特殊的迭代器。作为一个迭代器,生成器必须要定义一些方法,其中一个就是__next__()
。如同迭代器一样,我们可以使用__next__()
函数来获取下一个值。
生成器工作流程
下面就仔细看看生成器是怎么工作的。从上面的例子也可以看到,生成器函数跟普通的函数是有很大差别的。结合上面的例子我们加入一些打印信息,进一步看看生成器的执行流程:
def my_range(n):
print('开始迭代...')
i = 0
while i < n:
print('迭代中...')
yield i
i += 1
print('迭代结束...')
my_range = my_range(3)
# print(my_range)
print(next(my_range))
print(next(my_range))
print(next(my_range))
通过结果可以看到:
-
当调用生成器函数的时候,函数只是返回了一个生成器对象,并没有执行。
-
当
next()
方法第一次被调用的时候,生成器函数才开始执行,执行到yield
语句处停止:next()
方法的返回值就是yield
语句处的参数yielded value
-
当继续调用
next()
方法的时候,函数将接着上一次停止的yield
语句处继续执行,并到下一个yield
处停止;如果后面没有yield
就抛出StopIteration
异常
生成器表达式
在开始介绍生成器表达式之前,先看看我们比较熟悉的列表解析[List comprehensions]
,列表解析一般都是下面的形式。
[expr for iter_var in iterable if cond_expr]
迭代iterable
里所有内容,每一次迭代后,把iterable
里满足cond_expr
条件的内容放到iter_var
中,再在表达式expr
中获取iter_var
的内容,最后用表达式的计算值生成一个列表。
例如,生成一个list
包含50以内的所有奇数:
[i for i in range(50) if i % 2]
生成器表达式是在python2.4
中引入的,当序列过长, 而每次只需要获取一个元素时,应当考虑使用生成器表达式而不是列表解析。生成器表达式的语法和列表解析一样,只不过生成器表达式是被()
括起来的,而不是[]
,如下:
(expr for iter_var in iterable if cond_expr)
生成器表达式并不是创建一个列表, 而是返回一个生成器,这个生成器在每次计算出一个条目后,把这个条目"产生"(yield)
出来。 生成器表达式使用了"惰性计算"(lazy evaluation)
,只有在检索时才被赋值(evaluated)
,所以在列表比较长的情况下使用生成器会在内存上更有效。
gen = (i for i in range(10000) if i % 2)
print("__iter__" in dir(gen))
print("__next__" in dir(gen))
# 使用sum求和之后会导致再次迭代所获取的值为空
print(sum(gen))
print([i for i in gen])
生成器中的send()
与close()
方法
生成器中还有两个很重要的方法:send()
和close()
。
-
send(value)
:从前面了解到,
next()
方法可以恢复生成器状态并继续执行,其实send()
是除next()
外另一个恢复生成器的方法。Python 2.5
中,yield
语句变成了yield
表达式,也就是说yield
可以有一个值,而这个值就是send()
方法的参数,所以send(None)
和next()
是等效的。同样,next()
和send()
的返回值都是yield
语句处的参数(yielded value)
关于
send()
方法需要注意的是:调用send
传入非None
值前,生成器必须处于挂起状态,否则将抛出异常。也就是说,第一次调用时,要使用next()
语句或send(None)
,因为没有yield
语句来接收这个值。 -
close()
:这个方法用于关闭生成器,对关闭的生成器后再次调用
next
或send
将抛出StopIteration
异常。
def my_range(n):
i = 0
while i < n:
val = yield i
print('val is: ', val)
i += 1
my_range = my_range(5)
print(my_range.__next__())
print(my_range.__next__())
print(my_range.send('hello'))
my_range.close()
print(my_range.send('world'))
总结:
- 生成器是一种特殊的迭代器,内部支持了生成器协议,不需要明确定义
__iter__()
和__next__()
方法。 - 生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用
return
返回,而是用yield
一次返回一个结果。