多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
# 7.4. 使用 `{n,m}` 语法 * 7.4.1\. 校验十位数和个位数 在[前面的章节](roman_numerals.html "7.3. 个案研究:罗马字母"),你处理了相同字符可以重复三次的情况。在正则表达式中,有另外一个方式来表达这种情况,并且能提高代码的可读性。首先看看我们在前面的例子中使用的方法。 ## 例 7.5. 老方法:每一个字符都是可选的 ``` >>> import re >>> pattern = '^M?M?M?$' >>> re.search(pattern, 'M') <_sre.SRE_Match object at 0x008EE090> >>> pattern = '^M?M?M?$' >>> re.search(pattern, 'MM') <_sre.SRE_Match object at 0x008EEB48> >>> pattern = '^M?M?M?$' >>> re.search(pattern, 'MMM') <_sre.SRE_Match object at 0x008EE090> >>> re.search(pattern, 'MMMM') >>> ``` | | | | --- | --- | | \[1\] | 这个模式匹配串的开始,接着是第一个可选的字符 `M`,第二第三个 `M` 字符则被忽略 (这是可行的,因为它们都是可选的),最后是字符串的结尾。 | | \[2\] | 这个模式匹配串的开始,接着是第一和第二个可选字符 `M`,而第三个 `M` 字符被忽略 (这是可行的,因为它们都是可选的),最后匹配字符串的结尾。 | | \[3\] | 这个模式匹配字符串的开始,接着匹配所有的三个可选字符 `M`,最后匹配字符串的结尾。 | | \[4\] | 这个模式匹配字符串的开始,接着匹配所有的三个可选字符 `M`,但是不能够匹配字符串的结尾 (因为还有一个未匹配的字符 `M`),因此不能够匹配而返回一个 `None`。 | ## 例 7.6. 一个新的方法:从 `n` 到 `m` ``` >>> pattern = '^M{0,3}$' >>> re.search(pattern, 'M') <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MM') <_sre.SRE_Match object at 0x008EE090> >>> re.search(pattern, 'MMM') <_sre.SRE_Match object at 0x008EEDA8> >>> re.search(pattern, 'MMMM') >>> ``` | | | | --- | --- | | \[1\] | 这个模式意思是说:“匹配字符串的开始,接着匹配 0 到 3 个 `M` 字符,然后匹配字符串的结尾。”这里的 0 和 3 可以改成其它任何数字;如果你想要匹配至少 1 次,至多 3 次字符 `M`,则可以写成 `M{1,3}`。 | | \[2\] | 这个模式匹配字符串的开始,接着匹配三个可选 `M` 字符中的一个,最后是字符串的结尾。 | | \[3\] | 这个模式匹配字符串的开始,接着匹配三个可选 `M` 字符中的两个,最后是字符串的结尾。 | | \[4\] | 这个模式匹配字符串的开始,接着匹配三个可选 `M` 字符中的三个,最后是字符串的结尾。 | | \[5\] | 这个模式匹配字符串的开始,接着匹配三个可选 `M` 字符中的三个,但是_没有匹配上_ 字符串的结尾。正则表达式在字符串结尾之前最多只允许匹配三次 `M` 字符,但是实际上有四个 `M` 字符,因此模式没有匹配上这个字符串,返回一个 `None`。 | > 注意 > 没有一个轻松的方法来确定两个正则表达式是否等价。你能采用的最好的办法就是列出很多的测试样例,确定这两个正则表达式对所有的相关输入都有相同的输出。在本书后面的章节,将更多地讨论如何编写测试样例。 ## 7.4.1. 校验十位数和个位数 现在我们来扩展一下关于罗马数字的正则表达式,以匹配十位数和个位数,下面的例子展示十位数的校验方法。 ## 例 7.7. 校验十位数 ``` >>> pattern = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)$' >>> re.search(pattern, 'MCMXL') <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MCML') <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MCMLX') <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MCMLXXX') <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MCMLXXXX') >>> ``` | | | | --- | --- | | \[1\] | 这个模式匹配字符串的开始,接着是第一个可选字符 `M`,接着是 `CM`,接着 `XL`,接着是字符串的结尾。请记住,`(A&#124;B&#124;C)` 这个语法的含义是“精确匹配 A、B 或者 C 其中的一个”。此处匹配了 `XL`,因此不再匹配 `XC` 和 `L?X?X?X?`,接着就匹配到字符串的结尾。`MCML` 表示罗马数字 `1940`。 | | \[2\] | 这个模式匹配字符串的开始,接着是第一个可选字符 `M`,接着是 `CM`,接着 `L?X?X?X?`。在模式 `L?X?X?X?` 中,它匹配 `L` 字符并且跳过所有可选的 `X` 字符,接着匹配字符串的结尾。`MCML` 表示罗马数字 `1950`。 | | \[3\] | 这个模式匹配字符串的开始,接着是第一个可选字符 `M`,接着是 `CM`,接着是可选的 `L` 字符和可选的第一个 `X` 字符,并且跳过第二第三个可选的 `X` 字符,接着是字符串的结尾。`MCMLX` 表示罗马数字 `1960`。 | | \[4\] | 这个模式匹配字符串的开始,接着是第一个可选字符 `M`,接着是 `CM`,接着是可选的 `L` 字符和所有的三个可选的 `X` 字符,接着匹配字符串的结尾。`MCMLXXX` 表示罗马数字 `1980`。 | | \[5\] | 这个模式匹配字符串的开始,接着是第一个可选字符`M`,接着是`CM`,接着是可选的 `L`字符和所有的三个可选的`X`字符,接着就_未能匹配_ 字符串的结尾ie,因为还有一个未匹配的`X` 字符。所以整个模式匹配失败并返回一个 `None`. `MCMLXXXX` 不是一个有效的罗马数字。 | 对于个位数的正则表达式有类似的表达方式,我将省略细节,直接展示结果。 ``` >>> pattern = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$' ``` 用另一种 `{n,m}` 语法表达这个正则表达式会如何呢?这个例子展示新的语法。 ## 例 7.8. 用 `{n,m}` 语法确认罗马数字 ``` >>> pattern = '^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$' >>> re.search(pattern, 'MDLV') <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MMDCLXVI') <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MMMDCCCLXXXVIII') <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'I') <_sre.SRE_Match object at 0x008EEB48> ``` | | | | --- | --- | | \[1\] | 这个模式匹配字符串的开始,接着匹配三个可选的 `M` 字符的一个,接着匹配 `D?C{0,3}`,此处,仅仅匹配可选的字符 `D` 和 0 个可选字符 `C`。继续向前匹配,匹配 `L?X{0,3}`,此处,匹配可选的 `L` 字符和 0 个可选字符 `X`,接着匹配 `V?I{0,3}`,此处,匹配可选的 V 和 0 个可选字符 `I`,最后匹配字符串的结尾。`MDLV` 表示罗马数字 `1555`。 | | \[2\] | 这个模式匹配字符串的开始,接着是三个可选的 `M` 字符的两个,接着匹配 `D?C{0,3}`,此处为一个字符 `D` 和三个可选 `C` 字符中的一个,接着匹配 `L?X{0,3}`,此处为一个 `L` 字符和三个可选 `X` 字符中的一个,接着匹配 `V?I{0,3}`,此处为一个字符 `V` 和三个可选 `I` 字符中的一个,接着匹配字符串的结尾。`MMDCLXVI` 表示罗马数字 `2666`。 | | \[3\] | 这个模式匹配字符串的开始,接着是三个可选的 `M` 字符的所有字符,接着匹配 `D?C{0,3}`,此处为一个字符 `D` 和三个可选 `C` 字符中所有字符,接着匹配 `L?X{0,3}`,此处为一个 `L` 字符和三个可选 `X` 字符中所有字符,接着匹配 `V?I{0,3}`,此处为一个字符 `V` 和三个可选 `I` 字符中所有字符,接着匹配字符串的结尾。`MMMDCCCLXXXVIII` 表示罗马数字`3888`,这个数字是不用扩展语法可以写出的最大的罗马数字。 | | \[4\] | 仔细看哪!(我像一个魔术师一样,“看仔细喽,孩子们,我将要从我的帽子中拽出一只兔子来啦!”) 这个模式匹配字符串的开始,接着匹配 3 个可选 `M` 字符的 0 个,接着匹配 `D?C{0,3}`,此处,跳过可选字符 `D` 并匹配三个可选 `C` 字符的 0 个,接着匹配 `L?X{0,3}`,此处,跳过可选字符 `L` 并匹配三个可选 `X` 字符的 0 个,接着匹配 `V?I{0,3}`,此处跳过可选字符 `V` 并匹配三个可选 `I` 字符的一个,最后匹配字符串的结尾。哇赛! | 如果你在第一遍就跟上并理解了所讲的这些,那么你做的比我还要好。现在,你可以尝试着理解别人大规模程序里关键函数中的正则表达式了。或者想象着几个月后回头理解你自己的正则表达式。我曾经做过这样的事情,但是它并不是那么有趣。 在下一节里,你将会研究另外一种正则表达式语法,它可以使你的表达式具有更好的可维持性。