什么是正则表达式
正则表达式(Regular Expression,简称 regex 或 regexp)是一种用于描述字符串模式的形式化语言。它让你可以用一个简短的"模式字符串"来匹配、搜索、替换、分割满足该模式的文本。
正则表达式(Regular Expression)
由普通字符和特殊元字符组成的模式字符串,描述满足某种规律的字符串集合。例如
\d+ 描述"一个或多个数字",可以匹配 42、007、12345 等任何数字序列。模式(Pattern)
正则表达式本身。每个模式定义了一个规则集合,用于描述某类字符串应具备的结构特征。
匹配(Match)
当一个字符串满足某个正则模式的规则时,称该字符串与模式匹配。匹配可以是整个字符串,也可以是字符串中的某个子串。
元字符(Metacharacter)
在正则表达式中具有特殊含义的字符(如
.、*、+、?、^、$、[、]、{、}、(、)、|、\),不表示字面字符。正则表达式能做什么
正则表达式的用途贯穿日常开发的方方面面:
文本搜索与匹配
- 验证邮箱地址格式
- 验证手机号、身份证号
- 检测字符串是否符合日期格式
- 在日志文件中搜索特定错误
文本提取与替换
- 从 HTML 中提取链接、图片地址
- 从日志中提取 IP 地址、时间戳
- 批量重命名变量(代码重构)
- 模板渲染(替换占位符)
正则引擎的工作原理
理解正则引擎的工作方式,是避免写出低效或错误正则的基础。
NFA 引擎(Non-deterministic Finite Automaton)
绝大多数现代正则引擎(Python re、JavaScript、Java、.NET、PCRE)采用 NFA 引擎。NFA 引擎是"以正则为主导"的:引擎从模式的第一个元素开始,尝试在文本中找到匹配;当某条路径失败时,引擎回溯(backtrack)并尝试其他路径。NFA 支持捕获组、反向引用、前瞻断言等高级特性,但复杂的模式可能触发指数级回溯。
DFA 引擎(Deterministic Finite Automaton)
DFA 引擎是"以文本为主导"的:同时追踪所有可能的匹配路径,保证线性时间复杂度(不会回溯)。DFA 用于某些命令行工具(如 grep 的默认模式)和正则库(如 Go 的 regexp 包、RE2)。DFA 不支持捕获组和反向引用,但永远不会出现灾难性回溯。
NFA 引擎的匹配过程可以用以下伪代码理解:
# 简化的 NFA 匹配示意(以 /ab*c/ 匹配 "abbc" 为例) # 模式:a b* c # 文本:a b b c # # 步骤1:模式 'a' → 匹配文本[0]='a' ✓ 前进 # 步骤2:模式 'b*' → 贪婪,吃掉 'bb' → 指针到文本[3]='c' # 步骤3:模式 'c' → 匹配文本[3]='c' ✓ 成功! # # 如果步骤3失败,引擎会回溯:b* 退还一个 'b',再次尝试
字面字符(Literal Characters)
除了元字符以外,正则中的字符都是字面字符——匹配自身。
/hello/
say hello to the world
字面字符串,精确匹配 "hello"
import re # 字面匹配,区分大小写 re.search(r'hello', 'say hello to the world') # 匹配 re.search(r'hello', 'say Hello to the world') # None!H 大写 # 忽略大小写需要加标志位 re.search(r'hello', 'say Hello to the world', re.IGNORECASE) # 匹配
点号(.)——最宽泛的元字符
. 匹配除换行符(\n)以外的任意单个字符。
/c.t/
cat cut c3t c t ct(不匹配,需要一个字符)
. 匹配 a、u、3、空格等任意字符,但需要恰好一个
# . 匹配任意单个字符(除换行) re.findall(r'c.t', 'cat cut c3t c\nt') # → ['cat', 'cut', 'c3t'](c\nt 不匹配,因为 . 不匹配换行) # 加 re.DOTALL 标志,让 . 也匹配换行 re.findall(r'c.t', 'cat cut c3t c\nt', re.DOTALL) # → ['cat', 'cut', 'c3t', 'c\nt']
WARNING(点号的常见误用)点号是"任意字符",不是"任意字母"或"任意数字"。
/192.168.1.1/ 会匹配 "192X168Y1Z1",因为 . 在这里不是字面点,而是任意字符。要匹配字面点号,需要转义:/192\.168\.1\.1/。
元字符与转义
当你需要匹配元字符本身时,在它前面加反斜杠 \ 进行转义:
| 元字符 | 字面含义匹配 | 示例 |
|---|---|---|
. | \. | 3\.14 匹配 "3.14" |
* | \* | a\*b 匹配 "a*b" |
+ | \+ | a\+b 匹配 "a+b" |
? | \? | done\? 匹配 "done?" |
^ | \^ | \^abc 匹配 "^abc" |
$ | \$ | \$100 匹配 "$100" |
( ) | \( \) | \(a\) 匹配 "(a)" |
[ ] | \[ \] | \[x\] 匹配 "[x]" |
{ } | \{ \} | \{n\} 匹配 "{n}" |
| | \| | a\|b 匹配 "a|b" |
\ | \\ | C:\\Users 匹配 "C:\Users" |
TIP(Python 原始字符串)在 Python 中,正则表达式的反斜杠会与字符串的反斜杠解析产生冲突。始终使用原始字符串
r"..." 来写正则:r"\d+" 而非 "\\d+"。前者更直观,避免双重转义的混乱。
量词(Quantifiers)
量词指定前面的元素(字符、字符类或分组)重复出现的次数。
*(零次或多次)匹配前面的元素零次或任意多次。
ab*c 可以匹配 "ac"(b 出现0次)、"abc"(1次)、"abbc"(2次)等。+(一次或多次)匹配前面的元素至少一次。
ab+c 只能匹配 "abc"、"abbc",不能匹配 "ac"(b 至少出现1次)。?(零次或一次)匹配前面的元素可选地出现一次。
colou?r 匹配 "color" 和 "colour"(u 可以有也可以没有)。{n}(恰好 n 次)精确匹配 n 次。
\d{4} 匹配恰好4位数字,如 "2026",但不匹配 "202" 或 "20261"。{n,}(至少 n 次)匹配 n 次或更多次。
\d{3,} 匹配3位或更多位数字。{n,m}(n 到 m 次)匹配 n 到 m 次(包含两端)。
\d{3,5} 匹配3位到5位数字。| 量词 | 等价写法 | 含义 | 示例 |
|---|---|---|---|
* | {0,} | 0次或多次 | go*gle → "ggle", "gogle", "google" |
+ | {1,} | 1次或多次 | \d+ → "1", "42", "999" |
? | {0,1} | 0或1次(可选) | https? → "http" 或 "https" |
{3} | — | 恰好3次 | [A-Z]{3} → "USA", "ABC" |
{2,4} | — | 2到4次 | \w{2,4} → "hi", "hey", "hello" |
# 量词示例 re.findall(r'\d+', '123 abc 45 6789') # → ['123', '45', '6789'] re.findall(r'\d{3}', '123 4567 89') # → ['123', '456'] (注意:7 单独不够3位,89 也不够) re.findall(r'\d{2,3}', '1 22 333 4444') # → ['22', '333', '444'] (4444 取前3位 '444',贪婪) # 量词 ? 的典型用法:可选部分 re.findall(r'https?://\S+', 'http://a.com and https://b.com') # → ['http://a.com', 'https://b.com']
选择(Alternation):| 符号
| 是"或"操作符,匹配左边或右边的模式:
/cat|dog|bird/g
I have a cat and a dog. No bird.
| 的优先级最低,整个左右两侧都是选项
# | 匹配左边或右边 re.findall(r'cat|dog', 'I have a cat and a dog') # → ['cat', 'dog'] # | 优先级最低——整个左右都是选项 re.findall(r'gray|grey', 'gray or grey?') # → ['gray', 'grey'] # 用括号限定 | 的作用范围 re.findall(r'gr(a|e)y', 'gray or grey?') # → ['a', 'e'] (返回的是捕获组内容!) # 用非捕获组 (?:...) 限定范围但不捕获 re.findall(r'gr(?:a|e)y', 'gray or grey?') # → ['gray', 'grey'] (返回整个匹配)
锚点(Anchors):位置而非字符
锚点不匹配任何字符,而是匹配字符串中的特定位置。
^(行首/字符串开头)默认匹配整个字符串的开头。开启多行模式(
re.MULTILINE)后,匹配每行行首。注意:在字符类 [...] 中,^ 表示"取反",不是锚点。$(行尾/字符串结尾)默认匹配整个字符串的结尾(在可能的换行符之前)。开启多行模式后,匹配每行行尾。
\b(单词边界)匹配单词字符(
\w)与非单词字符(\W)之间的位置,或字符串的开头/结尾与单词字符之间的位置。用于精确匹配完整单词,避免匹配到单词的一部分。\B(非单词边界)\b 的反义,匹配不在单词边界的位置。例如 \Bcat\B 匹配 "concatenate" 中间的 "cat",但不匹配单独的 "cat"。/\bcat\b/g
I have a cat. My concatenate function works.
\b 确保只匹配完整的单词 "cat",不匹配 "concatenate" 中的 "cat"
# ^ 和 $ 锚点 re.findall(r'^\d+', '123 abc') # → ['123'](从开头匹配数字) re.findall(r'\d+$', 'abc 456') # → ['456'](到结尾匹配数字) re.fullmatch(r'\d+', '12345') # 匹配(整串都是数字) re.fullmatch(r'\d+', '123abc') # None(含非数字) # \b 单词边界 re.findall(r'\bcat\b', 'cat cats concatenate') # → ['cat'](只有独立的 'cat' 匹配) re.findall(r'\bcat', 'cat cats concatenate') # → ['cat', 'cat']('cat' 和 'cats' 中都以单词边界开头)
第一个完整示例:验证日期格式
综合运用字面字符、量词和锚点,写一个日期格式验证器:
import re # 目标:验证 YYYY-MM-DD 格式 # YYYY:4位数字 # MM:01-12 # DD:01-31 DATE_PATTERN = re.compile( r'^\d{4}' # 年份:4位数字,从开头 r'-(0[1-9]|1[0-2])' # 月份:01-09 或 10-12 r'-(0[1-9]|[12]\d|3[01])' # 日期:01-09, 10-29, 30-31 r'$' # 到结尾 ) def is_valid_date(s): return bool(DATE_PATTERN.fullmatch(s)) is_valid_date('2026-03-26') # True is_valid_date('2026-13-01') # False(月份超范围) is_valid_date('2026-03-32') # False(日期超范围) is_valid_date('26-3-1') # False(格式不对) is_valid_date('2026-03-26 extra') # False(后面有多余内容)
INFO(正则的局限性)正则表达式验证格式,但不验证语义。上面的模式会接受 "2026-02-31"(2月没有31天),但这种语义验证需要代码逻辑处理。正则表达式是字符串模式工具,不是日期解析器。
Python 与 JavaScript 语法对比
| 特性 | Python | JavaScript |
|---|---|---|
| 字面量语法 | 无(必须用字符串) | /pattern/flags |
| 使用字符串 | r"pattern"(推荐) | new RegExp("pattern", "flags") |
| 预编译 | re.compile(r"pattern") | /pattern/(自动) |
| 标志位 | re.I | re.M 等常量 | 末尾字母 gi、gim 等 |
| 搜索首个 | re.search() | str.match()(无 g) |
| 搜索所有 | re.findall() | str.match(/re/g) |
| 替换 | re.sub() | str.replace() |
| 反向引用 | \1、\g<name> | $1、$<name> |
小结
本章要点
- 正则表达式是描述字符串模式的形式化语言;NFA 引擎支持高级特性但可能回溯,DFA 引擎线性时间但功能受限
- 字面字符匹配自身;元字符有特殊含义;反斜杠
\转义元字符为字面字符 - 点号
.匹配除换行外的任意单个字符;要匹配字面点用\. - 量词:
*(0+)、+(1+)、?(0或1)、{n,m}(n到m次) - 锚点:
^行首、$行尾、\b单词边界——匹配位置而非字符 - 选择
|是"或"操作符,优先级最低;用(?:...)限定范围 - Python 中始终使用原始字符串
r"..."写正则,避免双重转义