17.2. plural.py
, 第 1 阶段
你所针对的单词 (至少在英语中) 是字符串和字符。你还需要规则来找出不同的字符 (字母) 组合,并对它们进行不同的操作。这听起来像是正则表达式的工作。
例 17.1. plural1.py
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'
|
|
[1] |
好啦,这是一个正则表达式,但是它使用了你在 第 7 章 正则表达式 中未曾见过的语法。方括号的意思是 “完全匹配这些字符中的一个”。也就是说,[sxz] 意味着 “s ,或者 x ,再或者 z ”,但只是其中的一个。$ 应该不陌生,它意味着匹配字符串的结尾。也就是说,检查 noun 是否以 s ,x ,或者 z 结尾。 |
[2] |
re.sub 函数进行以正则表达式为基础的替换工作。让我们更具体地看看它。 |
例 17.2. re.sub
介绍
>>> import re
>>> re.search('[abc]', 'Mark')
<_sre.SRE_Match object at 0x001C1FA8>
>>> re.sub('[abc]', 'o', 'Mark')
'Mork'
>>> re.sub('[abc]', 'o', 'rock')
'rook'
>>> re.sub('[abc]', 'o', 'caps')
'oops'
|
|
[1] |
Mark 包含 a ,b ,或者 c 吗?是的,含有 a 。 |
[2] |
好的,现在找出 a ,b ,或者 c 并以 o 取代之。Mark 就变成 Mork 了。 |
[3] |
同一方法可以将 rock 变成 rook 。 |
[4] |
你可能认为它可以将 caps 变成 oaps ,但事实并非如此。re.sub 替换所有 的匹配项,并不只是第一个匹配项。因此正则表达式将会把 caps 变成 oops ,因为 c 和 a 都被转换为 o 了。 |
例 17.3. 回到 plural1.py
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'
|
|
[1] |
回到 plural 函数。你在做什么?你在以 es 取代字符串的结尾。换句话说,追加 es 到字符串。你可以通过字符串拼合做到相同的事,例如 noun + 'es' ,但是我使用正则表达式做这一切,既是为了保持一致,也是为了本章稍后你会明白的其它原因。 |
[2] |
仔细看看,这是另一个新的内容。^ 是方括号里面的第一个字符,这有特别的含义:否定。[^abc] 意味着 “ 除 a 、 b 、 和 c 以外的 任意单字符”。所以,[^aeioudgkprt] 意味着除 a 、 e 、 i 、 o 、 u 、 d 、 g 、 k 、 p 、 r 和 t 以外的任意字符。这个字符之后应该跟着一个 h ,然后是字符串的结尾。你在寻找的是以发音的 H 结尾的单词。 |
[3] |
这是一个相似的表达:匹配 Y 前面不是 a 、 e 、 i 、 o 和 u ,并以这个 Y 结尾的单词。你在查找的是以发 I 音的 Y 结尾的单词。 |
例 17.4. 正则表达式中否定的更多应用
>>> 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')
>>>
|
|
[1] |
vacancy 匹配这个正则表达式,因为它以 cy 结尾,并且 c 不在 a 、 e 、 i 、 o 和 u 之列。 |
[2] |
boy 不能匹配,因为它以 oy 结尾,并且你特别指出 y 之前的字符不可以是 o 。day 不能匹配是因为以 ay 结尾。 |
[3] |
pita 不匹配是因为不以 y 结尾。 |
例 17.5. 更多的 re.sub
>>> re.sub('y$', 'ies', 'vacancy')
'vacancies'
>>> re.sub('y$', 'ies', 'agency')
'agencies'
>>> re.sub('([^aeiou])y$', r'\1ies', 'vacancy')
'vacancies'
|
|
[1] |
正则表达式把 vacancy 变为 vacancies ,把 agency 变为 agencies ,这正是你想要的。注意,将 boy 变成 boies 是可行的,但是永远不会发生,因为 re.search 首先确定是否应该应用 re.sub 。 |
[2] |
顺便提一下,可以将两个正则表达式 (一个确定规则适用与否,一个应用规则) 合并在一起成为一个正则表达式。这便是合并后的样子。它的大部分已经很熟悉:你应用的是在 第 7.6 节 “个案研究:解析电话号码” 学过的记忆组 (remembered group) 记住 y 之前的字符。然后再替换字符串,你使用一个新的语法 \1 ,这意味着:“嘿!记得前面的第一个组吗?把它放这儿”。就此而言,记住了 y 之前的 c ,然后你做替换工作,你将 c 替换到 c 的位置,并将 ies 替换到 y 的位置。(如果你有不止一个组则可以使用 \2 或者 \3 等等。) |
正则表达式替换非常强大,并且 \1
语法使之更加强大。但是将整个操作放在一个正则表达式中仍然晦涩难懂,也不能与前面描述的复数规则直接呼应。你原来列出的规则,比如 “如果单词以 S,X 或者 Z 结尾,结尾追加 ES”。如果你在函数中看到两行代码描述 “如果单词以 S,X 或者 Z 结尾,结尾追加 ES”,更加直观些。