17.2. plural.py,阶段 1

您正在查看单词,至少在英语中,单词是由字符组成的字符串。您有一些规则,要求您找到不同的字符组合,然后对它们进行不同的处理。这听起来像是正则表达式的工作。

示例 17.1. plural1.py


import re

def plural(noun):                            
    if re.search('[sxz]$', noun):             1
        return re.sub('$', 'es', noun)        2
    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]表示“sxz”,但只能是其中之一。$应该很熟悉;它匹配字符串的结尾。所以您正在检查noun是否以sxz结尾。
2 这个re.sub函数执行基于正则表达式的字符串替换。让我们更详细地看一下。

示例 17.2. 介绍re.sub

>>> import re
>>> re.search('[abc]', 'Mark')   1
<_sre.SRE_Match object at 0x001C1FA8>
>>> re.sub('[abc]', 'o', 'Mark') 2
'Mork'
>>> re.sub('[abc]', 'o', 'rock') 3
'rook'
>>> re.sub('[abc]', 'o', 'caps') 4
'oops'
1 字符串Mark是否包含abc?是的,它包含a
2 好的,现在找到abc,并将其替换为oMark变为Mork
3 同样的函数将rock转换为rook
4 您可能认为这会将caps转换为oaps,但事实并非如此。re.sub会替换所有匹配项,而不仅仅是第一个匹配项。所以这个正则表达式将caps转换为oops,因为ca都被转换为o

示例 17.3. 回到plural1.py


import re

def plural(noun):                            
    if re.search('[sxz]$', noun):            
        return re.sub('$', 'es', noun)        1
    elif re.search('[^aeioudgkprt]h$', noun): 2
        return re.sub('$', 'es', noun)        3
    elif re.search('[^aeiou]y$', noun):      
        return re.sub('y$', 'ies', noun)     
    else:                                    
        return noun + 's'                    
1 回到plural函数。您在做什么?您正在用es替换字符串的结尾。换句话说,将es添加到字符串中。您可以使用字符串连接来实现相同的结果,例如noun + 'es',但我在这里使用正则表达式是为了保持一致性,原因将在本章后面说明。
2 仔细观察,这是另一个新的变化。方括号内的第一个字符^具有特殊含义:否定。[^abc]表示“abc以外的任何单个字符”。所以[^aeioudgkprt]表示除aeioudgkprt以外的任何字符。然后该字符后面需要跟h,再跟字符串结尾。您正在寻找以 H 结尾且 H 可发的音的单词。
3 这里的模式相同:匹配以 Y 结尾的单词,其中 Y 前面的字符不是aeiou。您正在寻找以 Y 结尾且发音像 I 的单词。

示例 17.4. 关于否定正则表达式的更多信息

>>> import re
>>> re.search('[^aeiou]y$', 'vacancy') 1
<_sre.SRE_Match object at 0x001C1FA8>
>>> re.search('[^aeiou]y$', 'boy')     2
>>> 
>>> re.search('[^aeiou]y$', 'day')
>>> 
>>> re.search('[^aeiou]y$', 'pita')    3
>>> 
1 vacancy匹配此正则表达式,因为它以cy结尾,并且c不是aeiou
2 boy不匹配,因为它以oy结尾,而您明确表示y前面的字符不能是oday不匹配,因为它以ay结尾。
3 pita不匹配,因为它不以y结尾。

示例 17.5. 关于re.sub的更多信息

>>> re.sub('y$', 'ies', 'vacancy')              1
'vacancies'
>>> re.sub('y$', 'ies', 'agency')
'agencies'
>>> re.sub('([^aeiou])y$', r'\1ies', 'vacancy') 2
'vacancies'
1 这个正则表达式将vacancy转换为vacancies,将agency转换为agencies,这正是您想要的。请注意,它也会将boy转换为boies,但这在函数中永远不会发生,因为您首先执行了re.search来确定是否应该执行此re.sub
2 顺便说一句,我想指出的是,可以将这两个正则表达式(一个用于确定规则是否适用,另一个用于实际应用规则)组合成一个正则表达式。它看起来像这样。大部分内容应该很熟悉:您正在使用一个记忆组,您在第 7.6 节“案例研究:解析电话号码”中学习过,用于记住y之前的字符。然后在替换字符串中,您使用一个新的语法\1,这意味着“嘿,您还记得第一个组吗?把它放在这里”。在这种情况下,您记住了y之前的c,然后在进行替换时,您用c替换c,用ies替换y。(如果您有多个记忆组,则可以使用\2\3等)。

正则表达式替换功能非常强大,\1语法使其更加强大。但是,将整个操作组合成一个正则表达式也更难阅读,并且它没有直接映射到您最初描述复数规则的方式。您最初制定的规则类似于“如果单词以 S、X 或 Z 结尾,则添加 ES”。如果您查看此函数,您会发现有两行代码表示“如果单词以 S、X 或 Z 结尾,则添加 ES”。没有比这更直接的了。