我们在上一个博客Python3 迭代器中说明了如何通过类的方式产生可迭代数据(__next__和__init__)。而,能否利用普通函数的机制产生可迭代的数据呢(比列表更加高效)?答案是肯定的,这就是python的生成器:
生成器的基本目的
就是利用函数产生可迭代对象(数据),期望的使用方式是这样的
def func():
lst = range(10)
pass # 增加具体的某种操作
iterObj = func();
for i in iterObj:
print(i); # 迭代操作
想想看哈,pass 位置如何处理才能返回一个可迭代对象?
- 直接return lst 当然是ok的。这样子的问题在于迭代器相对于列表的优势就完全没有了,所以我们希望不凭借list的结构直接产生一个迭代器。次想法可否?
答案是完全可以:yield语句
注意, yield 是语句! 是一个关键字,和def、for、if 是一样的东东。更改之后的代码是这样的
>>> def func():
... lst = range(10)
... for i in lst:
... yield(i) # 用不用括号都是一样的
...
>>> IterObj = func( )
>>> for it in IterObj :
... print(it)
...
0 1 2 3 4 5 6 7 8 9
任何包含yield语句的函数称为生成器(generator)。区别与普通函数有:
- 不像return 那样返回值,而是每次产生多个值。
- 每次产生一个值(使用yield语句),函数就会被冻结:即函数停在那点等待被重新唤醒。函数被重新唤醒后就从停止的那点继续执行。
高级用法
- 可递归形成递归生成器
# 伪代码的形式
def flatten (somelist):
if somelist has sublist:
for sublist in somelist:
flatten(sublist)
else:
yield somelist
这种伪代码很好理解,但是不是很好的python 语句。这种case可以利用异常来便捷的处理,好像大牛们都习惯这么用,开始还真有点不习惯
# try except 语句实现的迭代
>>> def flatten (somelist):
... try:
... for sublist in somelist: # if has sublist
... for element in flatten(sublist): # we need flatten it again
... yield element
... except TypeError: # it is an element
... yield somelist
...
>>> lst = [[1,2,[3,4]],5,6,[7,8,[9,10,[11]]]]
>>> print(list(flatten(lst)))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
- 更多的语法检测功能(列表中string元素的检测)
# 伪代码的形式
def flatten (somelist):
if somelist is not string:
if somelist has sublist:
for sublist in somelist:
flatten(sublist)
else:
yield somelist
具体的语句实现是这样的
# try except 语句实现
>>> def flatten (somelist):
... try:
... try:
... somelist +''# only string can do this add(字符串拼接操作)
... except TypeError: pass # not a string
... else: raise TypeError # is a string, end flatten operation
... for sublist in somelist: # if has sublist
... for element in flatten(sublist): # we need flatten it again
... yield element
... except TypeError: # it is an element
... yield somelist
...
>>> lst = [[1,2,[3,4]],5,6,[7,8,[9,10,[11]]],'abc',['d',['ef',['gh',['ijk']]]]]
>>> print(list(flatten(lst)))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 'abc', 'd', 'ef', 'gh', 'ijk']
好的,看到了这里,估计你也就会使用生成器了,采用生成器(yield函数)返回的迭代器(class),与自己用class生成的迭代器一样的使用,没有区别。