python3 生成器
generator
如果一个函数至少包含一个yield声明(当然它也可以包含其他yield或return),那么它就是一个generator。
yield和return都会让函数返回一些东西,区别在于,return声明彻底结束一个函数,而yield声明是暂停函数,保存它的所有状态,并且后续被调用后会继续执行。
generator函数和普通函数的区别
- generator函数包含一个以上的yield声明
- generator函数被调用的时候,会返回一个iterator对象,但是函数并不会立即开始执行
- __iter__()和__next__()方法被自动实现,所以可以使用next()函数对返回的此iterator对象进行迭代
- 一旦一个generator 执行到yield语句,generator函数暂停,程序控制流被转移到调用方
- 在对generator的连续调用之间,generator的本地变量和状态会被保存
- 最终,generator函数终止,再调用generator会引发StopIteration异常
一个generator示例
def my_gen():
n = 1
print('This is printed first')
# Generator function contains yield statements
yield n
n += 1
print('This is printed second')
yield n
n += 1
print('This is printed at last')
yield n
迭代:
a = my_gen()
print(next(a))
print(next(a))
print(next(a))
输出:
This is printed first
1
This is printed second
2
This is printed at last
3
在这个例子里变量n在每次调用之间都被记住了。和一般函数不同的是,在函数yield之后本地变量没有被销毁,而且,generator对象只能被这样迭代一次。
for循环迭代:
for item in my_gen():
print(item)
输出:
This is printed first
1
This is printed second
2
This is printed at last
3
有循环的generator
通常来说,generator都是和循环结合实现的,且这个循环带有一个终止条件。
def rev_str(my_str):
length = len(my_str)
for i in range(length - 1, -1, -1):
yield my_str[i]
for char in rev_str("hello"):
print(char)
输出:
o
l
l
e
h
generator 表达式
使用generator表达式可以很容易地创建简单的generator。
就像lambda函数可以创建匿名函数一样,generator函数创建一个匿名generator函数。
generator表达式的语法类似于python的list comprehension,只是方括号被替换为了圆括号而已。
list comprehension和generator表达式的主要区别在于,前者产生全部的list,后者每次仅产生一项。
generator有些懒惰,仅在接到请求的时候才会产生输出。因此,generator表达式比list comprehension更加节省内存。
my_list = [1, 3, 6, 10]
#列表生成式
L = [x**2 for x in my_list]
print(L) #[1, 9, 36, 100]
#生成器
a = (x**2 for x in my_list)
print(next(a)) #1
print(next(a)) #9
print(next(a)) #36
print(next(a)) #100
使用generator的优势
容易实现
相对于iterator类来说,generator的实现清晰、简洁。下面是用iterator实现一个2的指数函数:
class PowTwo:
def __init__(self, max=0):
self.max = max
def __iter__(self):
self.n = 0
return self
def __next__(self):
if self.n > self.max:
raise StopIteration
result = 2 ** self.n
self.n += 1
return result
generator这样实现:
def PowTwoGen(max = 0):
n = 0
while n < max:
yield 2 ** n
n += 1
generator自动跟踪实现细节,因此更加清晰、简洁。
节省内存
一个函数返回一个序列(sequence)的时候,会在内存里面把这个序列构建好再返回。如果这个序列包含很多数据的话,就过犹不及了。
而如果序列是以generator方式实现的,就是内存友好的,因为他每次只产生一个item。
代表无限的stream
generator是一个很棒的表示无限数据流的工具。无限数据流不能被保存在内存里面,并且因为generator每次产生一个item,它就可以表示无限数据流。
下面的代码可以产生所有的奇数:
def all_even():
n = 0
while True:
yield n
n += 2