7.4. 使用 {n,m} 语法
Description: Python from novice to pro
Keywords: Python, Dive Into Python, tutorial, object-oriented, programming, documentation, book, free

# 深入 Python :Dive Into Python 中文版 Python 从新手到专家 [Dip_5.4b_CPyUG_Release] | Find:

## 7.4. 使用 `{n,m}` 语法

在前面的章节，你处理了相同字符可以重复三次的情况。在正则表达式中，有另外一个方式来表达这种情况，并且能提高代码的可读性。首先看看我们在前面的例子中使用的方法。

### 例 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')
    >>>

| 这个模式匹配串的开始，接着是第一个可选的字符 `M`，第二第三个 `M` 字符则被忽略 (这是可行的，因为它们都是可选的)，最后是字符串的结尾。
| 这个模式匹配串的开始，接着是第一和第二个可选字符 `M`，而第三个 `M` 字符被忽略 (这是可行的，因为它们都是可选的)，最后匹配字符串的结尾。
| 这个模式匹配字符串的开始，接着匹配所有的三个可选字符 `M`，最后匹配字符串的结尾。
| 这个模式匹配字符串的开始，接着匹配所有的三个可选字符 `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')
    >>>

| 这个模式意思是说：“匹配字符串的开始，接着匹配 0 到 3 个 `M` 字符，然后匹配字符串的结尾。”这里的 0 和 3 可以改成其它任何数字；如果你想要匹配至少 1 次，至多 3 次字符 `M`，则可以写成 `M{1,3}`。
| 这个模式匹配字符串的开始，接着匹配三个可选 `M` 字符中的一个，最后是字符串的结尾。
| 这个模式匹配字符串的开始，接着匹配三个可选 `M` 字符中的两个，最后是字符串的结尾。
| 这个模式匹配字符串的开始，接着匹配三个可选 `M` 字符中的三个，最后是字符串的结尾。
| 这个模式匹配字符串的开始，接着匹配三个可选 `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')
    >>>

| 这个模式匹配字符串的开始，接着是第一个可选字符 `M`，接着是 `CM`，接着 `XL`，接着是字符串的结尾。请记住，`(A|B|C)` 这个语法的含义是“精确匹配 A、B 或者 C 其中的一个”。此处匹配了 `XL`，因此不再匹配 `XC` 和 `L?X?X?X?`，接着就匹配到字符串的结尾。`MCML` 表示罗马数字 `1940`。
| 这个模式匹配字符串的开始，接着是第一个可选字符 `M`，接着是 `CM`，接着 `L?X?X?X?`。在模式 `L?X?X?X?` 中，它匹配 `L` 字符并且跳过所有可选的 `X` 字符，接着匹配字符串的结尾。`MCML` 表示罗马数字 `1950`。
| 这个模式匹配字符串的开始，接着是第一个可选字符 `M`，接着是 `CM`，接着是可选的 `L` 字符和可选的第一个 `X` 字符，并且跳过第二第三个可选的 `X` 字符，接着是字符串的结尾。`MCMLX` 表示罗马数字 `1960`。
| 这个模式匹配字符串的开始，接着是第一个可选字符 `M`，接着是 `CM`，接着是可选的 `L` 字符和所有的三个可选的 `X` 字符，接着匹配字符串的结尾。`MCMLXXX` 表示罗马数字 `1980`。
| 这个模式匹配字符串的开始，接着是第一个可选字符`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>

| 这个模式匹配字符串的开始，接着匹配三个可选的 `M` 字符的一个，接着匹配 `D?C{0,3}`，此处，仅仅匹配可选的字符 `D` 和 0 个可选字符 `C`。继续向前匹配，匹配 `L?X{0,3}`，此处，匹配可选的 `L` 字符和 0 个可选字符 `X`，接着匹配 `V?I{0,3}`，此处，匹配可选的 V 和 0 个可选字符 `I`，最后匹配字符串的结尾。`MDLV` 表示罗马数字 `1555`。
| 这个模式匹配字符串的开始，接着是三个可选的 `M` 字符的两个，接着匹配 `D?C{0,3}`，此处为一个字符 `D` 和三个可选 `C` 字符中的一个，接着匹配 `L?X{0,3}`，此处为一个 `L` 字符和三个可选 `X` 字符中的一个，接着匹配 `V?I{0,3}`，此处为一个字符 `V` 和三个可选 `I` 字符中的一个，接着匹配字符串的结尾。`MMDCLXVI` 表示罗马数字 `2666`。
| 这个模式匹配字符串的开始，接着是三个可选的 `M` 字符的所有字符，接着匹配 `D?C{0,3}`，此处为一个字符 `D` 和三个可选 `C` 字符中所有字符，接着匹配 `L?X{0,3}`，此处为一个 `L` 字符和三个可选 `X` 字符中所有字符，接着匹配 `V?I{0,3}`，此处为一个字符 `V` 和三个可选 `I` 字符中所有字符，接着匹配字符串的结尾。`MMMDCCCLXXXVIII` 表示罗马数字`3888`，这个数字是不用扩展语法可以写出的最大的罗马数字。
| 仔细看哪！(我像一个魔术师一样，“看仔细喽，孩子们，我将要从我的帽子中拽出一只兔子来啦！”) 这个模式匹配字符串的开始，接着匹配 3 个可选 `M` 字符的 0 个，接着匹配 `D?C{0,3}`，此处，跳过可选字符 `D` 并匹配三个可选 `C` 字符的 0 个，接着匹配 `L?X{0,3}`，此处，跳过可选字符 `L` 并匹配三个可选 `X` 字符的 0 个，接着匹配 `V?I{0,3}`，此处跳过可选字符 `V` 并匹配三个可选 `I` 字符的一个，最后匹配字符串的结尾。哇赛！

如果你在第一遍就跟上并理解了所讲的这些，那么你做的比我还要好。现在，你可以尝试着理解别人大规模程序里关键函数中的正则表达式了。或者想象着几个月后回头理解你自己的正则表达式。我曾经做过这样的事情，但是它并不是那么有趣。

在下一节里，你将会研究另外一种正则表达式语法，它可以使你的表达式具有更好的可维持性。