Chapter 03 / 10

分组、捕获与反向引用

用括号提取匹配内容,用引用实现模式复用——正则表达式最强大的特性之一

捕获组(Capturing Group)

圆括号 () 除了分组之外,还会"捕获"匹配到的内容,使你可以在结果中提取它:

import re
m = re.search(r'(\d{4})-(\d{2})-(\d{2})', '生日:1990-06-15')
print(m.group(0))  # '1990-06-15'  整个匹配
print(m.group(1))  # '1990'        第1组
print(m.group(2))  # '06'          第2组
print(m.group(3))  # '15'          第3组
print(m.groups()) # ('1990', '06', '15')
/(\d{4})-(\d{2})-(\d{2})/
生日:1990-06-15
紫色 = 第1组、橙色 = 第2组、绿色 = 第3组

非捕获组(Non-Capturing Group)

如果只需要分组(用于量词或选择),不需要捕获,使用 (?:...)

(?:https?|ftp)://\S+    # 匹配 URL,不捕获协议
(?:ab){3}                # 匹配 "ababab",不创建捕获组
TIP养成使用 (?:...) 的习惯:不需要捕获时就用非捕获组,这样代码更清晰,引擎也可以省去存储捕获结果的开销。

命名捕获组(Named Group)

用数字引用捕获组容易出错,命名捕获组让代码更可读:

# Python 语法:(?P<name>...)
m = re.search(
    r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})',
    '2026-03-26'
)
print(m.group('year'))   # '2026'
print(m.group('month'))  # '03'
print(m.groupdict())     # {'year': '2026', 'month': '03', 'day': '26'}

# JavaScript ES2018 语法:(?<name>...)
const m = '2026-03-26'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/)
console.log(m.groups)  // { year: '2026', month: '03', day: '26' }

反向引用(Backreference)

在同一正则中,可以用 \1\2(Python: \1,JS: \1)引用前面捕获组匹配到的内容:

# 匹配重复单词(如 "the the")
\b(\w+)\s+\1\b

# 匹配对称的 HTML 标签
<(\w+)>.*?</\1>
/\b(\w+)\s+\1\b/g
the cat cat sat on the the the mat
检测重复单词——常用于文本校对

命名反向引用

# Python:\g<name> 或 (?P=name)
re.sub(r'(?P<word>\w+)\s+(?P=word)', r'\g<word>', text)

# JavaScript:\k<name>
/(?<tag>\w+).*\k<tag>/

在替换中使用捕获组

捕获组在替换操作中大放异彩,可以重新组织匹配到的内容:

# 将 YYYY-MM-DD 转换为 DD/MM/YYYY
result = re.sub(
    r'(\d{4})-(\d{2})-(\d{2})',
    r'\3/\2/\1',
    '今天是 2026-03-26'
)
# → '今天是 26/03/2026'

# JavaScript
'2026-03-26'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$3/$2/$1')
// → '26/03/2026'

分组嵌套与编号规则

捕获组按左括号出现的顺序编号(从 1 开始):

(a(b(c)))
#  1 2 3    编号
#  group(1) → 'abc'
#  group(2) → 'bc'
#  group(3) → 'c'

常见应用场景

小结