💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 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 章 _正则表达式_](../regular_expressions/index.html "第 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 节 “个案研究:解析电话号码”](../regular_expressions/phone_numbers.html "7.6. 个案研究:解析电话号码") 学过的记忆组 (remembered group) 记住 `y` 之前的字符。然后再替换字符串,你使用一个新的语法 `\1`,这意味着:“嘿!记得前面的第一个组吗?把它放这儿”。就此而言,记住了 `y` 之前的 `c` ,然后你做替换工作,你将 `c` 替换到 `c` 的位置,并将 `ies` 替换到 `y` 的位置。(如果你有不止一个组则可以使用 `\2` 或者 `\3` 等等。) | 正则表达式替换非常强大,并且 `\1` 语法使之更加强大。但是将整个操作放在一个正则表达式中仍然晦涩难懂,也不能与前面描述的复数规则直接呼应。你原来列出的规则,比如 “如果单词以 S,X 或者 Z 结尾,结尾追加 ES”。如果你在函数中看到两行代码描述 “如果单词以 S,X 或者 Z 结尾,结尾追加 ES”,更加直观些。