虽然生成器可以让你编写出优雅的代码,但它并不是不可或缺的。
生成器是一种使用普通函数语法定义的迭代器。
例1:创建一个将嵌套列表展开的函数
1 # 创建一个将嵌套列表展开的函数 2 # 给出的嵌套列表 nested:嵌套 3 nested = [[1,2],[3,4]] 4 5 def flatten(nested): 6 for sublist in nested: 7 for element in sublist: 8 yield element # yield每生成一个值后,函数都将冻结,被重新唤醒后,函数将从停止的地方开始继续执行 9 10 for n in flatten(nested): 11 print(n)
输出:
1
2
3
4
当然我们可以将for循环换成:
print(list(flatten(nested)))
输出:[1, 2, 3, 4]
例2:递归生成器
1 # 创建一个能够处理任意层嵌套的列表 2 def flatten(nested): 3 try: 4 for sublist in nested: 5 for element in flatten(nested): 6 yield element 7 except TypeError: 8 yield nested
处理递归时,有两种可能:基线条件和递归条件。
基线条件下,要求这个函数展开单个元素(如一个数)。这种情况下,for循环将引发TypeError异常(因为你试图迭代一个数),而这个生成器只生成一个元素。
如果要展开的是一个列表(或其他任何可迭代的对象),则需要:遍历所有的子列表(有些可能不是列表)并对它们调用flatten,然后使用另一个for循环展开后的子列表中的所有元素。
注意:
如果nested是字符串或类似字符串的对象,它就属于序列,因此不会引发TypeError异常,可你并不想对其进行迭代。
因为:
① 你想将类似于字符串的对象视为原子值,而不是应该展开的序列;
② 对这样的对象迭代会导致无穷递归。因为字符串的第一个元素是一个长度为1的字符串,而长度为1的字符串的第一个元素是字符串本身。
要处理这种问题,必须在生成器开头进行检查。
例3:添加检查对象是否类似于字符串
1 def flatten(nested): 2 try: 3 # 不迭代类似字符串的对象 4 try: nested + '' 5 except TypeError: pass 6 else: raise TypeError 7 for sublist in nested: 8 for element in flatten(nested): 9 yield element 10 except TypeError: 11 yield nested
如果表达式nested + ''引发了TypeError异常(说明nested不是类字符串对象),就忽略这种异常;
如果没有引发(说明nested是类字符串对象),则通过else引发。这样将在外部的except子句中原封不动地生成类似于字符串的对象(因为加的''对原字符串没有任何影响)。
当然,我们也可通过isinstance检查
例4:通用生成器
生成器是包含yield关键字的函数,但被调用时不会执行函数体内的代码,而是返回一个迭代器。
每次请求值时,都将执行生成器的代码,直到遇见yield或者return。
yield意味着应生成一个值;
return意味着生成器应停止执行(即不在生成值;当且仅当在生成器调用return时,才能不提供任何参数)