正则表达式(Regular Expression)是一种用单个字符串描述、匹配一系列符合某个句法规则的字符串的模式。在 Python 中,通过 re 模块来使用正则表达式,它是处理文本查找、替换、验证的核心工具。
要写好正则,必须先理解构成模式的几类基本元素:元字符、量词、字符类、分组和断言。
元字符
元字符是正则中具有特殊含义的字符,就像编程语言里的关键字。如果真想要匹配这些字符本身,需要加反斜杠 \ 转义。
| 元字符 | 含义 | 示例 |
|--------|------|------|
| . | 匹配除换行符外的任意一个字符 | a.b 匹配 "aab", "a1b", "a#b" 等 |
| ^ | 匹配字符串开头 | ^Hello 只匹配以 "Hello" 开头的行 |
| $ | 匹配字符串结尾 | end$ 只匹配以 "end" 结尾的行 |
| | 前一个字符重复0次或多次 | abc 匹配 "ac", "abc", "abbc" |
| + | 前一个字符重复1次或多次 | ab+c 匹配 "abc", "abbc",不匹配 "ac" |
| ? | 前一个字符重复0次或1次 | colou?r 匹配 "color" 和 "colour" |
| \ | 转义字符,使元字符变普通,或表示特殊序列(如 \d) | \. 匹配真正的点号 |
| | | 或运算符,左右表达式任选其一 | cat|dog 匹配 "cat" 或 "dog" |
| () | 分组,见下文分组详解 | (abc)+ 匹配 "abc", "abcabc" |
| [] | 字符类,匹配方括号内的任意字符 | [aeiou] 匹配任意元音字母 |
| {} | 量词,指定重复次数 | a{3} 匹配 "aaa" |
量词
量词用来指定前一个字符或分组的出现次数,它让模式描述“数量”变得简单。
- *
**:零次或多次(贪婪)。等价于{0,}。 +:一次或多次(贪婪)。等价于{1,}。?:零次或一次。等价于{0,1}。{n}:恰好 n 次。{n,}:至少 n 次。{n,m}:至少 n 次,至多 m 次。
贪婪与非贪婪
默认量词都是贪婪的,会尽可能多地匹配字符。如果在量词后加 ?,变为非贪婪(懒惰),尽可能少地匹配。
- 对于字符串
"<div>内容</div>": "<.*>"会匹配整个"<div>内容</div>"(贪婪)。"<.*?>"会匹配"<div>"和"</div>"分开(非贪婪)。
字符类
字符类用于定义匹配某个位置允许出现的字符集合。写在方括号 [] 中。
- 直接列举:
[abc]匹配 a、b、c 中任意一个。 - 范围表示:
[a-z]匹配任意小写字母,[0-9A-Fa-f]匹配十六进制数字。 - 取反:
[^a-z]匹配不是小写字母的任意字符(^在开头时表示排除)。 - 预定义字符类(转义序列):
\d:数字,等价于[0-9]\D:非数字\w:单词字符(字母、数字、下划线),等价于[a-zA-Z0-9_]\W:非单词字符\s:空白字符(空格、制表符、换行等)\S:非空白字符
注意:在 [] 内部,大多数字元字符失去特殊含义,只有 ^(开头取反)、-(范围)、](结束)和 \ 仍需要留意。
分组与捕获
圆括号 () 除了确定运算优先级,还用来捕获匹配的子串。
- 捕获分组:
(pattern)会将被匹配的文本存储到一个组中,之后可以通过re.search()结果的.group(编号)或.group('名称')获取。 - 例如,匹配电话号码并提取区号:
(\d{3})-(\d{8}),对"010-12345678"运行后,.group(1)得到010,.group(2)得到12345678。 - 命名捕获:
(?P<name>pattern)给分组起个名字,更清晰。 (?P<area>\d{3})-(?P<number>\d{8}),之后用.group('area')获取区号。- 非捕获分组:
(?:pattern)只用于优先级或量词控制,不保存匹配结果,不生成组号,性能更高。 - 如
(?:http|ftp)://会匹配协议部分但不单独捕获。 - 反向引用:在模式里可以用
\1、\2引用前面捕获组匹配到的文本,常用于匹配重复内容。 (\w+)\s+\1匹配连续出现的相同单词,如 "hello hello"。
断言(零宽断言)
断言用于限定当前位置前面或后面必须(或不必须)满足某种条件,但断言本身不消耗字符(零宽度),只做位置检查。
- 正向先行断言:
(?=pattern)
要求当前位置之后的字符能匹配 pattern。
foo(?=bar)匹配 "foo" 但仅当后面跟着 "bar"("foobar" 中的 foo 匹配,但 "foobaz" 不匹配)。- 负向先行断言:
(?!pattern)
要求当前位置之后的字符不能匹配 pattern。
foo(?!bar)匹配后面不是 "bar" 的 "foo"。- 正向后顾断言:
(?<=pattern)
要求当前位置之前的字符能匹配 pattern。
(?<=\$)\d+匹配美元符号后面的数字,如 "$123" 中的 123。- 负向后顾断言:
(?<!pattern)
要求当前位置之前的字符不能匹配 pattern。
(?<!\$)\d+匹配前面不是 $ 的数字。
注意:Python 的 re 模块支持后顾断言,但要求后顾中的模式必须是定长的(不能出现量词如 +、*,但可以用 {n} 定长)。不过替代的 regex 第三方库支持变长后顾。
掌握这些基础构件,你就具备了阅读和编写绝大多数正则表达式的核心能力。接下来通过 re 模块的常用方法验证理解,就会越用越顺手。