Chapter 06 / 10

标志位与多行模式

用标志位改变正则引擎的默认行为——大小写、全局、多行、DOTALL、详细模式

标志位概述

标志位(Flags)是修改正则引擎默认匹配行为的开关。不同语言的标志位语法不同,但含义基本一致。

标志位(Flag)
正则表达式的修饰符,改变引擎的默认行为(如大小写敏感性、多行匹配、点号是否匹配换行等)。Python 中以常量形式传入,可用 | 组合多个标志;JavaScript 中在正则字面量末尾追加字母(如 /re/gim)。
含义PythonJavaScriptPCRE
忽略大小写re.IGNORECASE / re.Ii(?i)
全局匹配(用 findall/finditerg
多行模式re.MULTILINE / re.Mm(?m)
点号全匹配(DOTALL)re.DOTALL / re.Ss(?s)
详细模式(注释)re.VERBOSE / re.X(?x)
ASCII 模式re.ASCII / re.A
Unicode 模式re.UNICODE / re.Uu
粘滞匹配y
dotAll(JS)s
字符类 v 模式(JS)v

忽略大小写(i)

让整个正则匹配时不区分大小写。这是最常用的标志位之一。

# Python
re.findall(r'python', 'Python PYTHON python', re.IGNORECASE)
# → ['Python', 'PYTHON', 'python']

# 在 compile 中指定
KEYWORD = re.compile(r'python', re.I)
KEYWORD.findall('I love Python and PYTHON')
# → ['Python', 'PYTHON']

// JavaScript
'Hello WORLD'.match(/hello world/i)  // → ['Hello WORLD']
'Hello WORLD hello'.match(/hello/gi) // → ['Hello', 'hello']
INFO(i 标志与字符类)re.IGNORECASE 会影响字符类中的字母范围。[a-z] 加上 re.I 后等价于 [a-zA-Z]。但字符类中的 ^(取反)同样受影响——[^a-z]re.I 会排除所有大小写字母。

多行模式(m)

默认情况下,^$ 匹配整个字符串的开头和结尾。开启 MULTILINE 后,它们匹配每一行的开头和结尾。

INFO(多行模式的精确含义)多行模式改变的是 ^$ 锚点的语义,而不是. 匹配换行符(那是 DOTALL 的功能)。这两个功能相互独立,可以同时启用。
text = """第一行数据
第二行数据
第三行数据"""

# 默认:^ 只匹配整个字符串的开头
re.findall(r'^\S+', text)
# → ['第一行数据']  (只有第一行)

# 多行模式:^ 匹配每行的开头
re.findall(r'^\S+', text, re.MULTILINE)
# → ['第一行数据', '第二行数据', '第三行数据']

# $ 同理
re.findall(r'\S+$', text, re.MULTILINE)
# → ['第一行数据', '第二行数据', '第三行数据']
/^\d+\./gm(多行)
1. 苹果
2. 香蕉
3. 橙子
多行模式下 ^ 匹配每行行首,提取每行的序号
# 实用示例:提取日志文件中每行的时间戳
log = """2026-03-26 10:00:01 INFO Started
2026-03-26 10:00:05 ERROR Failed
2026-03-26 10:00:10 INFO Done"""

re.findall(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}', log, re.MULTILINE)
# → ['2026-03-26 10:00:01', '2026-03-26 10:00:05', '2026-03-26 10:00:10']

# 替换每行的前缀
re.sub(r'^', '> ', log, flags=re.MULTILINE)
# 在每行开头添加 '> '

DOTALL 模式(s)

. 匹配包括换行符在内的任意字符,适合处理多行内容。

# 默认:. 不匹配换行
re.search(r'start.*end', 'start\nend')
# → None(. 无法跨越 \n)

# DOTALL:. 也匹配换行
re.search(r'start.*end', 'start\nend', re.DOTALL)
# → 匹配 'start\nend'

# 提取跨行的 HTML 注释
html = """<!--
  This is a
  multi-line comment
-->"""

re.findall(r'<!--.*?-->', html, re.DOTALL)
# → ['<!--\n  This is a\n  multi-line comment\n-->']

# 不使用 re.DOTALL 的替代方案:[\s\S]* 匹配任意字符(包括换行)
re.findall(r'<!--[\s\S]*?-->', html)
# 等效结果(在不支持 DOTALL 的环境中用)

详细模式(x / re.VERBOSE)

详细模式让你可以在正则表达式中添加空格和注释(# 开头),大幅提升复杂正则的可读性。

INFO(详细模式规则)开启 re.VERBOSE 后:① 空格(除非在字符类中或用 \ 转义)会被忽略;② # 到行尾的内容视为注释;③ 字符类 [...] 内部的空格不受影响。如需匹配字面空格,用 \ (反斜杠+空格)或 \s
# 不使用详细模式(可读性差)
EMAIL = re.compile(r'^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$')

# 使用详细模式(清晰的注释)
EMAIL = re.compile(r"""
    ^                           # 字符串开头
    [a-zA-Z0-9._%+\-]+          # 用户名:字母、数字、._%+-
    @                           # @ 符号
    [a-zA-Z0-9.\-]+             # 域名:字母、数字、点、连字符
    \.                          # 字面点号
    [a-zA-Z]{2,}                # 顶级域名:2位或更多字母
    $                           # 字符串结尾
""", re.VERBOSE)

# 详细模式中的空格和注释
re.search(r"""
    \d+     # 数字
    [\s,]+  # 空白或逗号
    \d+     # 更多数字
""", '123, 456', re.VERBOSE)
# 匹配 '123, 456'

组合多个标志

# Python:用 | 组合多个标志
re.compile(r'pattern', re.IGNORECASE | re.MULTILINE | re.DOTALL)

# 或使用简写
re.compile(r'pattern', re.I | re.M | re.S)

# 同时使用详细模式和其他标志
PATTERN = re.compile(r"""
    (?P<year>\d{4})   # 年
    -
    (?P<month>\d{2})  # 月
    -
    (?P<day>\d{2})    # 日
""", re.VERBOSE | re.ASCII)

// JavaScript:在末尾叠加多个标志字母
/pattern/gims   // 全局+忽略大小写+多行+DOTALL

内联标志(Inline Flags)

内联标志嵌入在正则内部,只影响该位置之后(或括号内)的部分:

# 影响整个模式(放在开头)
re.search(r'(?i)hello', 'HELLO')   # 匹配

# 只影响括号内
re.findall(r'(?i:hello) world', 'HELLO world')   # 匹配
re.findall(r'(?i:hello) world', 'HELLO WORLD')   # None(world 区分大小写)

# 多个内联标志组合
re.search(r'(?im)^python', 'PYTHON\ncode')  # i(忽略大小写)+ m(多行)

# 取消标志(Python 支持)
re.search(r'(?i)hello(?-i)world', 'HELLOWORLD')
# None:hello 不区分大小写,world 区分大小写
re.search(r'(?i)hello(?-i)world', 'HELLOworld')
# 匹配 'HELLOworld'

JavaScript 特有标志

y 标志(粘滞匹配)

const re = /\d+/y
re.lastIndex = 0

'123abc456'.match(re)  // → ['123'],lastIndex 自动更新为 3
re.lastIndex           // → 3

// 下一次从 lastIndex=3 开始,且必须从该位置精确匹配
'123abc456'.match(re)  // → null(位置3是 'a',不是数字)

// 对比 g 标志:g 标志会跳过不匹配的位置继续搜索
'123abc456'.match(/\d+/g)  // → ['123', '456'](跳过 abc)

v 标志(ES2024,扩展字符类)

// v 标志启用新的字符类特性(不能与 u 同时使用)
/[\p{Decimal_Number}--[0-9]]/v  // 差集:Unicode 数字减去 ASCII 数字
/[\p{ASCII}&&\p{Letter}]/v      // 交集:ASCII 且是字母

re.ASCII vs re.UNICODE

Python 3 中正则默认以 Unicode 模式运行,\w\d\s 匹配 Unicode 范围的字符。加 re.ASCII 可以限制为仅 ASCII 范围:

re.UNICODE(默认)
Python 3 的默认行为。\w 匹配任何 Unicode 字母/数字/下划线(包括中文、日文、韩文等);\d 匹配任何 Unicode 数字(包括全角数字 0-9);\s 匹配任何 Unicode 空白字符。
re.ASCII(re.A)
\w\d\s 及其大写反义限制为只匹配 ASCII 范围([a-zA-Z0-9_][0-9][ \t\n\r\f\v])。适合只处理英文文本或需要严格 ASCII 兼容的场景。
# 默认(Unicode 模式)
re.findall(r'\w+', 'hello 你好 123')
# → ['hello', '你好', '123']  (汉字也是 \w)

# ASCII 模式
re.findall(r'\w+', 'hello 你好 123', re.ASCII)
# → ['hello', '123']  (只匹配 ASCII 单词字符)

# 对 \d 的影响
re.findall(r'\d+', 'price: 260元')         # → ['260'](全角数字)
re.findall(r'\d+', 'price: 260元', re.ASCII) # → [](只有半角数字才算)
WARNING(MULTILINE 与 DOTALL 的常见混淆)多行模式(re.MULTILINE/m)和 DOTALL(re.DOTALL/s)控制的是不同的行为,经常被混淆:MULTILINE 只改变 ^$ 的锚点含义;DOTALL 只改变 . 是否匹配换行符。两者可以独立或组合使用。如果你想"匹配跨行的任意内容",需要的是 DOTALL,而不是 MULTILINE。

标志位的实际应用场景

应用场景推荐标志原因
关键词搜索(不区分大小写)re.I用户输入可能大小写混用
提取每行行首标记re.M^ 需要匹配每行开头
提取多行 HTML/JSON 内容re.S. 需要跨越换行
写复杂的生产级正则re.X可读性和注释
处理纯 ASCII 表单验证re.A排除全角字符干扰
日志分析(同时多行+忽略大小写)re.I | re.M组合使用
# 综合示例:解析多行配置文件
config = """
# 数据库配置
HOST = localhost
PORT = 5432
DATABASE = mydb  # 生产数据库
"""

# 详细模式 + ASCII 模式:提取所有键值对,忽略注释
KEY_VALUE = re.compile(r"""
    ^               # 行首(需要 re.M)
    (?!\#)          # 不是注释行(负向前瞻)
    (\w+)           # 键名
    \s*=\s*         # 等号(两边允许空白)
    ([^#\n]+?)      # 值(到 # 或换行前,非贪婪)
    \s*$            # 到行尾(允许尾随空白)
""", re.VERBOSE | re.MULTILINE | re.ASCII)

KEY_VALUE.findall(config)
# → [('HOST', 'localhost'), ('PORT', '5432'), ('DATABASE', 'mydb')]

小结

本章要点