复数名词。以及返回其它函数的函数、高级正则表达式和生成器。
import re
def plural(noun):
if re.search('[sxz]$', noun): ①
return re.sub('$', 'es', noun) ②
elif re.search('[^aeioudgkprt]h$', noun):
return re.sub('$', 'es', noun)
elif re.search('[^aeiou]y$', noun):
return re.sub('y$', 'ies', noun)
else:
return noun + 's'
- re.search()是查询
- 中括号表示“匹配这些字符的其中之一”。因此 [sxz] 的意思是: “s、 x 或 z”,但只匹配其中之一。
- re.sub() 函数执行基于正则表达式的字符串替换。
否定”正则表达式
>>> import re
>>> re.search('[^aeiou]y$', 'vacancy') ①
<_sre.SRE_Match object at 0x001C1FA8>
>>> re.search('[^aeiou]y$', 'boy') ②
>>>
>>> re.search('[^aeiou]y$', 'day')
>>>
>>> re.search('[^aeiou]y$', 'pita') ③
>>>
- vacancy 匹配该正则表达式,因为它以 cy 结尾,且 c 并非 a、 e、 i、 o 或 u。
- boy 不匹配,因为它以 oy 结尾,可以明确地说 y 之前的字符不能是 o 。day 不匹配,因为它以 ay 结尾。
最终版本:
我还想指出可以将该两条正则表达式合并起来(一条查找是否应用该规则,另一条实际应用规则),使其成为一条正则表达式。用到的记忆分组。该分组用于保存字母 y 之前的字符。然后在替换字符串中,用到了新的语法: \1,它表示“嘿,记住的第一个分组呢?把它放到这里。”在此例中, 记住了 y 之前的 c ,在进行替换时,将用 c 替代 c,用 ies 替代 y 。(如果有超过一个的记忆分组,可以使用 \2 和 \3 等等。)
>>> re.sub('([^aeiou])y$', r'\1ies', 'vacancy') ②
'vacancies'
函数列表,闭合
import re
def build_match_and_apply_functions(pattern, search, replace): ①
def matches_rule(word):
return re.search(pattern, word)
def apply_rule(word):
return re.sub(search, replace, word)
return (matches_rule, apply_rule)
rules = []
with open('plural4-rules.txt', encoding='utf-8') as pattern_file: ②
for line in pattern_file: ③
pattern, search, replace = line.split(None, 3) ④
rules.append(build_match_and_apply_functions( ⑤
pattern, search, replace))
- build_match_and_apply_functions() 函数没有发生变化。仍然使用了闭合技术:通过外部函数中定义的变量来动态创建两个函数。
- 全局的 open() 函数打开文件并返回一个文件对象。此例中,将要打开的文件包含了名词复数形式的模式字符串。with 语句创建了叫做 context【上下文】的东西:当 with 块结束时,Python 将自动关闭文件,即便是在 with 块中引发了例外也会这样。在 《文件》 一章中将学到关于 with 块和文件对象的更多内容。
- for line in 代码从打开的文件中读取数据,并将文本赋值给 line 变量。在 《文件》 一章中将学到更多关于读取文件的内容。
- 文件中每行都有三个值,单它们通过空白分隔(制表符或空白,没有区别)。要将它们分开,可使用字符串方法 split() 。split() 方法的第一个参数是 None,表示“对任何空白字符进行分隔(制表符或空白,没有区别)”。第二个参数是 3,意思是“针对空白分隔三次,丢弃该行剩下的部分。”像 [sxz]
es 这样的行将被分割为列表 [‘[sxz]
′,′ ’, ‘es’],意思是 pattern 获得值 ‘[sxz] ′,search获得值′ ’,而 replace 获得值 ‘es’。对于短短的一行代码来说确实威力够大的。 - 最后,将 pattern 、 search 和 replace 传入 build_match_and_apply_functions() 函数,它将返回一个函数的元组。将该元组添加到 rules 列表,最终 rules 将储存 plural() 函数所预期的匹配和应用函数列表。
此处的改进是将复数形式规则独立地放到了一份外部文件中,因此可独立于使用它的代码单独对规则进行维护。代码是代码,数据是数据,生活更美好。
生成器(generator)
如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)。
创建
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator。
如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:
yield 命令的意思是这不是一个普通的函数。它是一次生成一个值的特殊类型函数。可以将其视为可恢复函数。即等价于一个中断。
最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
>>> def odd():
... print 'step 1'
... yield 1
... print 'step 2'
... yield 3
... print 'step 3'
... yield 5
...
使用
generator的next()方法,所以,我们创建了一个generator后,基本上永远不会调用next()方法,而是通过for循环来迭代它。正确的方法是使用for循环。
>>> for n in fib(6):
... print n
优缺点
获得了什么呢?启动时间。在第四步中引入 plural4 模块时,它读取了整个模式文件,并创建了一份所有可能规则的列表,甚至在考虑调用 plural() 函数之前。有了生成器,可以轻松地处理所有工作:可以读取规则,创建函数并试用它们,如果该规则可用甚至可以不读取文件剩下的部分或创建更多的函数。
失去了什么?性能!每次调用 plural() 函数,rules() 生成器将从头开始——这意味着重新打开模式文件,并从头开始读取,每次一行。
要是能够两全其美多好啊:最低的启动成本(无需对 import 执行任何代码),同时 最佳的性能(无需一次次地创建同一函数)。哦,还需将规则保存在单独的文件中(因为代码和数据要泾渭分明),还有就是永远不必两次读取同一行。
加强
在 python2.5 中,一些加强特性加入到生成器中,所以除了 next()来获得下个生成的值,用户可以将值回送给生成器[send()],在生成器中抛出异常,以及要求生成器退出[close()]
def gen(x):
count = x
f = gen(5)
print f.send(9)#发送数字9给生成器
参考:
http://old.sebug.net/paper/books/dive-into-python3/generators.html