17.7. plural.py,阶段 6

现在您准备好讨论生成器了。

例 17.17. plural6.py


import re

def rules(language):                                                                 
    for line in file('rules.%s' % language):                                         
        pattern, search, replace = line.split()                                      
        yield lambda word: re.search(pattern, word) and re.sub(search, replace, word)

def plural(noun, language='en'):      
    for applyRule in rules(language): 
        result = applyRule(noun)      
        if result: return result      

这使用了一种称为生成器的技术,在您先看一个更简单的例子之前,我甚至不会尝试解释它。

例 17.18. 生成器简介

>>> def make_counter(x):
...     print 'entering make_counter'
...     while 1:
...         yield x               1
...         print 'incrementing x'
...         x = x + 1
...     
>>> counter = make_counter(2) 2
>>> counter                   3
<generator object at 0x001C9C10>
>>> counter.next()            4
entering make_counter
2
>>> counter.next()            5
incrementing x
3
>>> counter.next()            6
incrementing x
4
1 make_counteryield 关键字的存在意味着这不是一个普通的函数。它是一种特殊类型的函数,一次生成一个值。您可以将其视为可恢复函数。调用它将返回一个生成器,该生成器可用于生成 x 的连续值。
2 要创建 make_counter 生成器的实例,只需像调用任何其他函数一样调用它。请注意,这实际上不会执行函数代码。您可以分辨出来,因为 make_counter 的第一行是一个 print 语句,但还没有打印任何内容。
3 make_counter 函数返回一个生成器对象。
4 第一次在生成器对象上调用 next() 方法时,它会执行 make_counter 中直到第一个 yield 语句的代码,然后返回生成的值。在这种情况下,这将是 2,因为您最初是通过调用 make_counter(2) 创建生成器的。
5 在生成器对象上重复调用 next()从您上次停止的地方继续,并继续执行,直到遇到下一个 yield 语句。等待执行的下一行代码是打印 incrementing xprint 语句,然后是实际递增它的 x = x + 1 语句。然后再次循环遍历 while 循环,您要做的第一件事是 yield x,它返回 x 的当前值(现在为 3)。
6 第二次调用 counter.next() 时,您将再次执行所有相同操作,但这次 x 现在为 4。依此类推。由于 make_counter 设置了一个无限循环,理论上您可以永远这样做,它只会不断递增 x 并输出值。但让我们看看生成器的更多有效用途。

例 17.19. 使用生成器代替递归


def fibonacci(max):
    a, b = 0, 1       1
    while a < max:
        yield a       2
        a, b = b, a+b 3
1 斐波那契数列是一个数字序列,其中每个数字都是其前面两个数字的总和。它从 01 开始,开始时增长缓慢,然后越来越快。要开始序列,您需要两个变量:a0 开始,b1 开始。
2 a 是序列中的当前数字,因此生成它。
3 b 是序列中的下一个数字,因此将其赋值给 a,但也计算下一个值 (a+b) 并将其赋值给 b 以供以后使用。请注意,这是并行发生的;如果 a3b5,则 a, b = b, a+b 会将 a 设置为 5b 的先前值),并将 b 设置为 8ab 的先前值的总和)。

所以你有一个函数可以吐出连续的斐波那契数。当然,您可以使用递归来做到这一点,但这种方式更容易阅读。此外,它与 for 循环配合良好。

例 17.20. for 循环中的生成器

>>> for n in fibonacci(1000): 1
...     print n,              2
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
1 您可以直接在 for 循环中使用像 fibonacci 这样的生成器。for 循环将创建生成器对象并连续调用 next() 方法来获取要分配给 for 循环索引变量 (n) 的值。
2 每次遍历 for 循环时,n 都会从 fibonacci 中的 yield 语句中获取一个新值,您所要做的就是将其打印出来。一旦 fibonacci 用完了数字(a 大于 max,在本例中为 1000),for 循环就会正常退出。

好的,让我们回到 plural 函数,看看您是如何使用它的。

例 17.21. 生成动态函数的生成器


def rules(language):                                                                 
    for line in file('rules.%s' % language):                                          1
        pattern, search, replace = line.split()                                       2
        yield lambda word: re.search(pattern, word) and re.sub(search, replace, word) 3

def plural(noun, language='en'):      
    for applyRule in rules(language):  4
        result = applyRule(noun)      
        if result: return result      
1 for line in file(...) 是一种常见的习惯用法,用于从文件中逐行读取行。它之所以有效,是因为 file 实际上返回一个生成器,其 next() 方法返回文件的下一行。这太酷了,我光是想想就激动不已。
2 这里没有魔法。请记住,规则文件的行包含三个由空格分隔的值,因此 line.split() 返回一个包含 3 个值的元组,您将这些值分配给 3 个局部变量。
3 然后你 yield。 你 yield 什么?一个使用 lambda 动态构建的函数,它实际上是一个闭包(它使用局部变量 patternsearchreplace 作为常量)。换句话说,rules 是一个生成规则函数的生成器。
4 由于 rules 是一个生成器,因此您可以直接在 for 循环中使用它。第一次遍历 for 循环时,您将调用 rules 函数,该函数将打开规则文件,从中读取第一行,动态构建一个匹配并应用规则文件中定义的第一条规则的函数,并生成动态构建的函数。第二次遍历 for 循环时,您将从 rules 中断的地方继续(在 for line in file(...) 循环的中间),读取规则文件的第二行,动态构建另一个匹配并应用规则文件中定义的第二条规则的函数,并生成它。依此类推。

阶段 5 相比,您获得了什么?在阶段 5 中,您在尝试第一个规则之前就读取了整个规则文件并构建了所有可能规则的列表。现在使用生成器,您可以懒惰地完成所有事情:您打开第一个规则并读取第一条规则并创建一个函数来尝试它,但如果它有效,您就永远不会读取文件的其余部分或创建任何其他函数。

延伸阅读