Python 正则表达式超详细入门指南
欢迎来到正则表达式(Regular Expression,简称 Regex 或 RE)的世界!如果你经常处理文本数据,无论是验证输入格式、从大量文本中提取特定信息,还是进行复杂的文本替换,正则表达式都是一个极其强大且不可或缺的工具。Python 通过其内置的 re
模块提供了对正则表达式的完整支持。
本指南将带你从零开始,逐步深入 Python 正则表达式的核心概念、常用语法以及 re
模块的使用方法,力求提供一个全面而详细的入门体验。
目录
- 什么是正则表达式?为什么使用它?
- Python 中的
re
模块 - 基本匹配:字面量与特殊字符
- 字面量匹配
- 点号
.
:匹配任意字符 - 反斜杠
\
:转义特殊字符 - 原始字符串
r"..."
:Python 与正则表达式的完美结合
- 字符集合(Character Sets)
- 方括号
[]
:匹配指定集合中的任意一个字符 - 连字符
-
:表示范围 - 脱字符
^
:在字符集合中表示否定 - 常用预定义字符集
\d
:数字\D
:非数字\w
:单词字符\W
:非单词字符\s
:空白字符\S
:非空白字符
- 方括号
- 量词(Quantifiers)
- 星号
*
:匹配零次或多次 - 加号
+
:匹配一次或多次 - 问号
?
:匹配零次或一次 - 花括号
{}
:精确匹配指定次数{n}
:恰好 n 次{n,}
:至少 n 次{n,m}
:至少 n 次,至多 m 次
- 贪婪与非贪婪匹配
- 星号
- 锚点(Anchors)
- 脱字符
^
:匹配字符串或行的开头 - 美元符号
$
:匹配字符串或行的结尾 - 单词边界
\b
- 非单词边界
\B
- 脱字符
- 分组与捕获(Grouping & Capturing)
- 圆括号
()
:分组与捕获 - 通过 Match Object 获取捕获的内容
- 非捕获分组
(?:...)
- 圆括号
- 选择符(Alternation)
- 竖线
|
:匹配多个模式中的任意一个
- 竖线
- Python
re
模块的常用函数re.search(pattern, string, flags=0)
:扫描整个字符串并返回第一个匹配re.match(pattern, string, flags=0)
:从字符串的开始位置匹配re.findall(pattern, string, flags=0)
:查找字符串中所有非重叠的匹配项re.finditer(pattern, string, flags=0)
:查找所有匹配项并返回迭代器re.sub(pattern, repl, string, count=0, flags=0)
:替换匹配项re.split(pattern, string, maxsplit=0, flags=0)
:按匹配项分割字符串re.compile(pattern, flags=0)
:编译正则表达式
- Match Object(匹配对象)
group([group1, ...])
groups()
start([group])
end([group])
span([group])
- 常用标志(Flags)
re.IGNORECASE
或re.I
re.MULTILINE
或re.M
re.DOTALL
或re.S
- 实践建议与总结
1. 什么是正则表达式?为什么使用它?
正则表达式是一种用来描述、匹配一系列符合某个句法规则的字符串的单个字符串。简单来说,它是一个强大的文本模式匹配工具。你可以用一个字符串模式(即正则表达式)来检查另一个字符串是否符合某种规则,或者从中提取想要的部分。
为什么使用正则表达式?
想象一下你需要完成以下任务:
* 检查用户输入的邮箱地址格式是否正确。
* 从一篇网页文章中提取所有的电话号码。
* 找到所有出现过的日期(如 YYYY-MM-DD 格式)。
* 将文本中的所有连续空白字符(空格、制表符等)替换成单个空格。
* 分析日志文件,提取特定类型的错误信息。
如果不用正则表达式,你可能需要写很多 if/else
判断、循环、字符串切片等复杂且容易出错的代码。而正则表达式提供了一种简洁、高效且灵活的方式来完成这些任务。它就像一种专门用于文本处理的“微型编程语言”。
2. Python 中的 re
模块
Python 标准库中的 re
模块提供了所有正则表达式的功能。要使用它,只需要简单地导入:
python
import re
接下来,我们将看到 re
模块中的各种函数如何与正则表达式模式配合使用。
3. 基本匹配:字面量与特殊字符
最简单的正则表达式就是由普通字符组成的,它们会匹配字符串中完全相同的字面量。
字面量匹配
“`python
import re
text = “hello world”
pattern = “hello”
match = re.search(pattern, text) # 在 text 中搜索 pattern
if match:
print(f”找到匹配: {match.group()}”) # match.group() 返回匹配到的字符串
else:
print(“未找到匹配”)
输出: 找到匹配: hello
“`
在这个例子中,模式 "hello"
直接匹配了文本中的 "hello"
。
点号 .
:匹配任意字符 (除了换行符)
点号 .
是第一个遇到的特殊字符。它匹配除换行符 \n
之外的任何单个字符。
“`python
text = “cat\ncbt\ncdt”
pattern = “c.t” # 匹配 c 后面跟任意一个字符,再跟 t
matches = re.findall(pattern, text) # 查找所有匹配项
print(matches)
输出: [‘cat’, ‘cbt’, ‘cdt’]
text_with_newline = “cat\ndog”
pattern_dot = “c.t”
match_dot = re.search(pattern_dot, text_with_newline)
if match_dot:
print(f”找到: {match_dot.group()}”) # 匹配 ‘cat’
else:
print(“未找到”)
pattern_dot_newline = “cat.dog” # 这里的 . 不会匹配换行符 \n
match_dot_newline = re.search(pattern_dot_newline, text_with_newline)
if match_dot_newline:
print(f”找到: {match_dot_newline.group()}”)
else:
print(“未找到”) # 未找到
输出:
找到: cat
未找到
``
.
注意:不匹配换行符是默认行为。你可以使用
re.DOTALL(或
re.S) 标志来改变这个行为,让
.` 也能匹配换行符(后面会讲到)。
反斜杠 \
:转义特殊字符
如果想匹配正则表达式中的特殊字符本身(如 .
, *
, +
, ?
, (
, )
, [
, ]
, {
, }
, ^
, $
, |
, \
),你需要用反斜杠 \
来转义它们。
“`python
text = “访问地址是: http://www.example.com”
pattern_literal_dot = “http://www.example.com” # 使用 . 匹配字面量的点号
match = re.search(pattern_literal_dot, text)
if match:
print(f”找到字面量点号匹配: {match.group()}”)
输出: 找到字面量点号匹配: http://www.example.com
``
.
在这个例子中,匹配的是字符串中的字面量点号
.`,而不是“任意字符”。
原始字符串 r"..."
:Python 与正则表达式的完美结合
在 Python 字符串中,反斜杠 \
也是一个特殊字符,用于转义(如 \n
表示换行,\t
表示制表符)。这与正则表达式中 \
的用途冲突了!
例如,正则表达式中 \d
表示数字。如果你在 Python 字符串中写 "\\d"
,Python 会将其解释为字面量 \
后跟 d
;如果你写 "\d"
,这本身在 Python 中可能没有特殊含义,但当 Python 将这个字符串传递给 re
模块时,re
会把它看作 \d
。这很容易混淆,特别是当正则表达式中包含大量反斜杠时(如文件路径 C:\Users\Name
)。
为了避免这种“反斜杠危机”,Python 引入了原始字符串。以 r
开头的字符串,如 r"..."
,会将其中的反斜杠视为字面量字符,不再进行 Python 层的转义。这使得编写正则表达式变得更加直观和安全。
强烈建议在编写正则表达式模式时,始终使用原始字符串。
“`python
没有使用原始字符串 (不推荐)
pattern = “\d+” # Python 会解释成 “\d+” 然后传递给 re
pattern = “\d+” # 这看起来像正则,但 Python 内部处理可能有歧义
使用原始字符串 (推荐)
pattern_digits = r”\d+” # r”” 告诉 Python \d 就是字面量 \d,由 re 模块去解释它为数字
text = “电话号码: 123-456-7890”
match = re.search(pattern_digits, text)
if match:
print(f”找到数字序列: {match.group()}”)
输出: 找到数字序列: 123
``
r”…”`。
从现在开始,本指南中的正则表达式模式都将使用原始字符串
4. 字符集合(Character Sets)
字符集合允许你匹配一个位置上可能出现的多个字符中的任意一个。
方括号 []
:匹配指定集合中的任意一个字符
方括号 []
定义了一个字符集合。它会匹配 []
中包含的任意一个字符。
“`python
text = “colour color coworker co-worker”
pattern = r”colou?r” # 匹配 color 或 colour (这里用了 ? 量词,后面会讲)
matches = re.findall(pattern, text)
print(f”‘colou?r’ 匹配: {matches}”) # 输出: [‘colour’, ‘color’]
pattern_set = r”co[lw]r” # 匹配 colr 或 cowr
matches = re.findall(pattern_set, text)
print(f”‘co[lw]r’ 匹配: {matches}”) # 输出: [‘color’, ‘cowr’]
``
[abc]匹配 'a'、'b' 或 'c'。
[0123456789]` 匹配任意一个数字。
连字符 -
:表示范围
在字符集合内部,连字符 -
可以用来表示一个字符范围。
“`python
text = “abc 123 xyz ABC”
pattern_range_lower = r”[a-z]+” # 匹配一个或多个小写字母
matches = re.findall(pattern_range_lower, text)
print(f”‘[a-z]+’ 匹配: {matches}”) # 输出: [‘abc’, ‘xyz’]
pattern_range_upper = r”[A-Z]+” # 匹配一个或多个大写字母
matches = re.findall(pattern_range_upper, text)
print(f”‘[A-Z]+’ 匹配: {matches}”) # 输出: [‘ABC’]
pattern_range_digit = r”[0-9]+” # 匹配一个或多个数字
matches = re.findall(pattern_range_digit, text)
print(f”‘[0-9]+’ 匹配: {matches}”) # 输出: [‘123’]
pattern_multiple_ranges = r”[a-zA-Z0-9]+” # 匹配一个或多个字母或数字
matches = re.findall(pattern_multiple_ranges, text)
print(f”‘[a-zA-Z0-9]+’ 匹配: {matches}”) # 输出: [‘abc’, ‘123’, ‘xyz’, ‘ABC’]
“`
脱字符 ^
:在字符集合中表示否定
在字符集合 []
的开头使用脱字符 ^
表示匹配不在该集合中的任意字符。
“`python
text = “这是一个测试字符串 123 。”
pattern_not_digit = r”[^0-9]” # 匹配任意一个非数字字符
matches = re.findall(pattern_not_digit, text)
输出会包含所有非数字字符,包括空格和中文标点
print(f”‘[^0-9]’ 匹配 (部分): {matches[:10]}…”) # 打印前10个
pattern_not_vowel = r”[^aeiouAEIOU]” # 匹配任意一个非元音字母
text_english = “This is a test string.”
matches = re.findall(pattern_not_vowel, text_english)
输出会包含所有非元音字母、空格、标点等
print(f”‘[^aeiouAEIOU]’ 匹配 (部分): {matches[:10]}…”)
pattern_not_whitespace = r”[^\s]+” # 匹配一个或多个非空白字符
matches = re.findall(pattern_not_whitespace, text)
print(f”‘[^\s]+’ 匹配: {matches}”) # 输出: [‘这是一个测试字符串’, ‘123’, ‘。’]
``
^
请注意,在字符集合内部和外部的含义是不同的。在
[]外部,
^是一个锚点(匹配开头),而在
[]` 内部开头,它是否定。
常用预定义字符集
正则表达式提供了一些方便的预定义字符集,它们是常用字符集合的简写形式。
\d
: 匹配任意一个数字 (0-9)。等价于[0-9]
。\D
: 匹配任意一个非数字字符。等价于[^0-9]
。\w
: 匹配任意一个“单词字符”(字母、数字或下划线_
)。等价于[a-zA-Z0-9_]
。\W
: 匹配任意一个非“单词字符”。等价于[^a-zA-Z0-9_]
。\s
: 匹配任意一个空白字符(空格、制表符\t
、换页符\f
、回车符\r
、换行符\n
等)。\S
: 匹配任意一个非空白字符。
这些预定义字符集非常常用,能让你的正则表达式更简洁。
“`python
text = “Date: 2023-10-27, Time: 10:30:00. User: user_123.”
pattern_date = r”\d{4}-\d{2}-\d{2}” # 匹配 YYYY-MM-DD 格式
match_date = re.search(pattern_date, text)
if match_date:
print(f”找到日期: {match_date.group()}”) # 输出: 找到日期: 2023-10-27
pattern_word = r”\w+” # 匹配一个或多个单词字符
matches_word = re.findall(pattern_word, text)
print(f”找到单词字符序列: {matches_word}”) # 输出: [‘Date’, ‘2023’, ’10’, ’27’, ‘Time’, ’10’, ’30’, ’00’, ‘User’, ‘user_123’]
pattern_non_whitespace = r”\S+” # 匹配一个或多个非空白字符
matches_non_whitespace = re.findall(pattern_non_whitespace, text)
print(f”找到非空白字符序列: {matches_non_whitespace}”)
输出: [‘Date:’, ‘2023-10-27,’, ‘Time:’, ’10:30:00.’, ‘User:’, ‘user_123.’]
“`
5. 量词(Quantifiers)
量词用于指定在其前面的元素(一个字符、一个字符集、一个分组等)应该出现多少次才能构成匹配。
-
*
: 匹配前面的元素 零次或多次。
python
text = "abbbbc abc ab abbbbbbc"
pattern = r"ab*c" # 匹配 a 后面跟零个或多个 b,再跟 c
matches = re.findall(pattern, text)
print(f"'ab*c' 匹配: {matches}") # 输出: ['abbbbc', 'ac', 'abbbbbbc'] ('ac' 是因为 b 出现了零次) -
+
: 匹配前面的元素 一次或多次。
python
text = "abbbbc abc ab abbbbbbc"
pattern = r"ab+c" # 匹配 a 后面跟一次或多次 b,再跟 c
matches = re.findall(pattern, text)
print(f"'ab+c' 匹配: {matches}") # 输出: ['abbbbc', 'abc', 'abbbbbbc'] ('ab' 不匹配,因为 b 没有出现一次以上) -
?
: 匹配前面的元素 零次或一次。
“`python
text = “colour color”
pattern = r”colou?r” # 匹配 colou 出现零次或一次,然后跟 r
matches = re.findall(pattern, text)
print(f”‘colou?r’ 匹配: {matches}”) # 输出: [‘colour’, ‘color’]text = “Nov November”
pattern = r”Nov(ember)?” # 匹配 Nov 后面跟 (ember) 零次或一次 (这里用了分组,后面会讲)
matches = re.findall(pattern, text) # findall 对分组的行为需要注意,这里会返回分组内部内容
print(f”‘Nov(ember)?’ findall: {matches}”) # 输出: [”, ’ember’] – findall with groups returns tuples/group content使用 search 更直观理解匹配
match_nov = re.search(r”Nov(ember)?”, “Nov”)
match_november = re.search(r”Nov(ember)?”, “November”)
print(f”Search ‘Nov(ember)?’ on ‘Nov’: {match_nov.group()}”) # 输出: Nov
print(f”Search ‘Nov(ember)?’ on ‘November’: {match_november.group()}”) # 输出: November
“` -
{n}
: 匹配前面的元素 恰好 n 次。
python
text = "aaa aa a aaaa"
pattern = r"a{3}" # 匹配恰好连续出现 3 次的 a
matches = re.findall(pattern, text)
print(f"'a{{3}}' 匹配: {matches}") # 输出: ['aaa', 'aaa'] (从 'aaaa' 中找到一个 'aaa') -
{n,}
: 匹配前面的元素 至少 n 次。
python
text = "aaa aa a aaaa aaaaa"
pattern = r"a{3,}" # 匹配至少连续出现 3 次的 a
matches = re.findall(pattern, text)
print(f"'a{{3,}}' 匹配: {matches}") # 输出: ['aaa', 'aaaa', 'aaaaa'] -
{n,m}
: 匹配前面的元素 至少 n 次,至多 m 次。
python
text = "aaa aa a aaaa aaaaa"
pattern = r"a{2,4}" # 匹配至少连续出现 2 次,至多 4 次的 a
matches = re.findall(pattern, text)
print(f"'a{{2,4}}' 匹配: {matches}") # 输出: ['aaa', 'aa', 'aaaa', 'aaaa'] (从 'aaaaa' 中找到一个 'aaaa')
贪婪与非贪婪匹配
默认情况下,所有量词(*
, +
, ?
, {,}
) 都是贪婪的 (Greedy)。这意味着它们会尽可能多地匹配字符,同时仍然使整个模式匹配成功。
python
text = "<h1>Title</h1>"
pattern_greedy = r"<.*>" # 匹配 < 后面跟任意字符零次或多次,再跟 >
match_greedy = re.search(pattern_greedy, text)
print(f"贪婪匹配 '<.*>': {match_greedy.group()}") # 输出: <h1>Title</h1> (匹配了整个字符串)
这通常不是我们想要的结果。我们可能只想匹配 <h1.>
或 </h1.>
。
要使量词变为非贪婪的 (Non-Greedy) 或懒惰的 (Lazy),可以在量词后面加上一个问号 ?
。
*?
: 匹配零次或多次,但尽可能少+?
: 匹配一次或多次,但尽可能少??
: 匹配零次或一次,但尽可能少{n,}?
: 至少 n 次,但尽可能少{n,m}?
: 至少 n 次,但尽可能少
“`python
text = “
Title
”
pattern_non_greedy = r”<.?>” # 匹配 < 后面跟任意字符零次或多次 (非贪婪),再跟 >
match_non_greedy1 = re.search(pattern_non_greedy, text)
print(f”非贪婪匹配 ‘<.?>’ (第一次): {match_non_greedy1.group()}”) # 输出:
找到下一个匹配
match_non_greedy2 = re.search(pattern_non_greedy, text, match_non_greedy1.end()) # 从上一个匹配结束的位置开始搜索
print(f”非贪婪匹配 ‘<.*?>’ (第二次): {match_non_greedy2.group()}”) # 输出:
使用 findall 更直观
matches_non_greedy = re.findall(pattern_non_greedy, text)
print(f”非贪婪匹配 ‘<.*?>’ findall: {matches_non_greedy}”) # 输出: [‘
‘, ‘
‘]
“`
非贪婪匹配非常重要,特别是在处理成对标记(如 HTML/XML 标签)时。
6. 锚点(Anchors)
锚点不匹配任何字符,它们匹配的是位置。
-
^
: 匹配字符串的开头。如果在多行模式下 (使用re.M
标志),它也会匹配每一行的开头。
“`python
text = “First line\nSecond line\nThird line”
pattern_start = r”^First” # 匹配以 “First” 开头的行/字符串
match_start = re.search(pattern_start, text)
print(f”匹配开头 (默认): {match_start.group()}”) # 输出: First在多行模式下
pattern_start_m = r”^Second” # 匹配以 “Second” 开头的行
match_start_m = re.search(pattern_start_m, text, re.MULTILINE) # 使用 re.M 标志
print(f”匹配开头 (多行模式): {match_start_m.group()}”) # 输出: Second
“` -
$
: 匹配字符串的结尾。如果在多行模式下 (使用re.M
标志),它也会匹配每一行的结尾。
“`python
text = “First line\nSecond line\nThird line”
pattern_end = r”line$” # 匹配以 “line” 结尾的行/字符串
match_end = re.search(pattern_end, text)
print(f”匹配结尾 (默认): {match_end.group()}”) # 输出: line (匹配 Third line 的 line)在多行模式下
pattern_end_m = r”line$”
matches_end_m = re.findall(pattern_end_m, text, re.MULTILINE) # 使用 re.M 标志
print(f”匹配结尾 (多行模式): {matches_end_m}”) # 输出: [‘line’, ‘line’, ‘line’]
“` -
\b
: 匹配单词边界。单词边界是指一个单词字符 (\w
) 和一个非单词字符 (\W
) 之间的位置,或者字符串的开头/结尾与一个单词字符之间的位置。它是一个零宽度断言,不消耗任何字符。
“`python
text = “cat catheter category”
pattern_word_boundary = r”\bcat\b” # 匹配独立的单词 “cat”
matches = re.findall(pattern_word_boundary, text)
print(f”‘\bcat\b’ 匹配: {matches}”) # 输出: [‘cat’]text = “category concat cat cat.”
pattern_boundary = r”\bcat” # 匹配以 “cat” 开头的单词
matches = re.findall(pattern_boundary, text)
print(f”‘\bcat’ 匹配: {matches}”) # 输出: [‘cat’, ‘cat’]pattern_boundary_end = r”cat\b” # 匹配以 “cat” 结尾的单词
matches = re.findall(pattern_boundary_end, text)
print(f”‘cat\b’ 匹配: {matches}”) # 输出: [‘cat’, ‘cat’]
“` -
\B
: 匹配非单词边界。与\b
相反,匹配单词字符和单词字符之间,或非单词字符和非单词字符之间的位置。
“`python
text = “cat catheter category”
pattern_non_boundary = r”\Bcat” # 匹配不是以 “cat” 开头的 “cat”
matches = re.findall(pattern_non_boundary, text)
print(f”‘\Bcat’ 匹配: {matches}”) # 输出: [‘cat’] (匹配 catheter 中的 cat)pattern_non_boundary_end = r”cat\B” # 匹配不是以 “cat” 结尾的 “cat”
matches = re.findall(pattern_non_boundary_end, text)
print(f”‘cat\B’ 匹配: {matches}”) # 输出: [‘cat’] (匹配 catheter 中的 cat)
“`
7. 分组与捕获(Grouping & Capturing)
圆括号 ()
在正则表达式中有两个主要用途:
1. 分组 (Grouping):将多个元素视为一个整体,可以对其应用量词。
2. 捕获 (Capturing):捕获括号内匹配到的内容,以便后续提取。
“`python
text = “abab ababab”
pattern_group = r”(ab)+” # 将 ‘ab’ 作为一个组,匹配该组出现一次或多次
matches = re.findall(pattern_group, text) # findall 遇到分组会只返回分组内部的内容
print(f”‘(ab)+’ findall: {matches}”) # 输出: [‘ab’, ‘ab’] – 只返回每个匹配中最后一个完整组的内容
使用 search 来看整体匹配
match_group = re.search(r”(ab)+”, text)
if match_group:
print(f”‘(ab)+’ search 整体匹配: {match_group.group(0)}”) # group(0) 或 group() 返回整个匹配
print(f”‘(ab)+’ search 第1组捕获: {match_group.group(1)}”) # group(1) 返回第1个捕获组的内容 (这里是最后一个重复的 ‘ab’)
输出:
‘(ab)+’ search 整体匹配: abab
‘(ab)+’ search 第1组捕获: ab
``
(ab)+
在中,
(ab)是一个组,量词
+作用于整个组,表示匹配一个或多个
ab`。
通过 Match Object 获取捕获的内容
当使用 re.search()
或 re.match()
时,如果找到匹配,它们会返回一个 Match Object。这个对象有方法可以获取捕获组的内容。
match.group(index)
: 返回指定索引的捕获组的内容。index
为 0 表示整个匹配的字符串,1 表示第一个捕获组,2 表示第二个,依此类推。match.groups()
: 返回一个包含所有捕获组内容的元组。
“`python
text = “My phone number is 123-456-7890.”
模式分解电话号码,分成区号、前三位、后四位
pattern_phone = r”(\d{3})-(\d{3})-(\d{4})”
match_phone = re.search(pattern_phone, text)
if match_phone:
print(f”整个匹配: {match_phone.group(0)}”) # 或 match_phone.group()
print(f”区号: {match_phone.group(1)}”)
print(f”前三位: {match_phone.group(2)}”)
print(f”后四位: {match_phone.group(3)}”)
print(f”所有分组: {match_phone.groups()}”)
输出:
整个匹配: 123-456-7890
区号: 123
前三位: 456
后四位: 7890
所有分组: (‘123’, ‘456’, ‘7890’)
“`
非捕获分组 (?:...)
如果你只需要将一些元素进行分组以便应用量词或选择符,但不需要捕获这个分组的内容,可以使用非捕获分组 (?:...)
。这可以提高一点效率,并且在使用 findall
时不会包含这个分组的内容。
“`python
text = “cat dog mouse”
pattern_non_capturing = r”(?:cat|dog)s?” # 匹配 cat 或 dog,后面可选跟 s
matches = re.findall(pattern_non_capturing, text)
print(f”‘(?:cat|dog)s?’ findall: {matches}”) # 输出: [‘cat’, ‘dog’] – 只返回整体匹配,因为没有捕获组
text = “cats dogs”
matches = re.findall(pattern_non_capturing, text)
print(f”‘(?:cat|dog)s?’ findall on ‘cats dogs’: {matches}”) # 输出: [‘cats’, ‘dogs’]
对比捕获分组 findall 的行为
pattern_capturing = r”(cat|dog)s?”
matches_capturing = re.findall(pattern_capturing, text)
print(f”‘(cat|dog)s?’ findall on ‘cats dogs’: {matches_capturing}”) # 输出: [‘cat’, ‘dog’] – findall 只返回捕获组内容
“`
8. 选择符(Alternation)
竖线 |
就像逻辑 OR,允许你在多个模式中选择匹配任意一个。
python
text = "apple banana orange grape"
pattern_fruits = r"apple|banana|orange" # 匹配 "apple" 或 "banana" 或 "orange"
matches = re.findall(pattern_fruits, text)
print(f"'apple|banana|orange' 匹配: {matches}") # 输出: ['apple', 'banana', 'orange']
当 |
和其他模式组合时,它的作用范围是整个模式,除非使用分组 ()
来限制其范围。
“`python
text = “pineapple apple”
pattern_wrong = r”pine|apple” # 匹配 “pine” 或 “apple”
matches = re.findall(pattern_wrong, text)
print(f”‘pine|apple’ 匹配: {matches}”) # 输出: [‘pine’, ‘apple’]
pattern_correct = r”(pine)?apple” # 匹配可选的 “pine” 后面跟 “apple”
matches = re.findall(pattern_correct, text)
print(f”‘(pine)?apple’ 匹配: {matches}”) # 输出: [”, ”] # findall on capturing group returns group content
使用 search 来看整体匹配
match_pineapple = re.search(r”(pine)?apple”, text)
print(f”Search ‘(pine)?apple’ on ‘pineapple’: {match_pineapple.group(0)}”) # 输出: pineapple
match_apple = re.search(r”(pine)?apple”, text, match_pineapple.end()) # 从上一个匹配结束位置继续搜
print(f”Search ‘(pine)?apple’ on ‘pineapple’ (second match): {match_apple.group(0)}”) # 输出: apple
“`
9. Python re
模块的常用函数
现在我们了解了正则表达式的基本语法,来看看如何在 Python 中实际使用它们。
re.search(pattern, string, flags=0)
扫描 string
查找 pattern
的 第一个 匹配项。如果找到,返回一个 Match Object;如果没有找到,返回 None
。
“`python
text = “The quick brown fox jumps over the lazy dog.”
pattern = r”fox”
match = re.search(pattern, text)
if match:
print(f”Found ‘{match.group()}’ at position {match.start()} to {match.end()}.”)
print(f”Span: {match.span()}”)
else:
print(“Pattern not found.”)
输出: Found ‘fox’ at position 16 to 19. Span: (16, 19)
“`
re.match(pattern, string, flags=0)
从 string
的 开始位置 尝试匹配 pattern
。如果字符串的开头与模式匹配,返回一个 Match Object;否则返回 None
。请注意它和 search
的区别,match
只关心字符串的开头。
“`python
text = “The quick brown fox”
pattern_match = r”The”
pattern_search = r”fox”
match_obj_match = re.match(pattern_match, text)
match_obj_search = re.match(pattern_search, text) # 从开头匹配 ‘fox’,失败
if match_obj_match:
print(f”re.match found: {match_obj_match.group()}”) # 输出: re.match found: The
else:
print(“re.match failed.”)
if match_obj_search:
print(f”re.match found: {match_obj_search.group()}”)
else:
print(“re.match failed for ‘fox’.”) # 输出: re.match failed for ‘fox’.
如果使用 re.search
search_obj_search = re.search(pattern_search, text)
if search_obj_search:
print(f”re.search found: {search_obj_search.group()}”) # 输出: re.search found: fox
``
re.match()相当于在模式前隐式地加上了
^` 锚点。
re.findall(pattern, string, flags=0)
查找 string
中所有与 pattern
匹配的非重叠子串,并将它们作为一个列表返回。
如果模式中包含捕获组,findall
将返回一个元组列表,每个元组包含各捕获组的内容(对于单个捕获组,只返回该组内容的列表)。如果模式中没有捕获组,findall
返回匹配到的完整字符串列表。
“`python
text = “The price is $10.50 and the quantity is 5.”
pattern_digits = r”\d+” # 没有捕获组
matches_digits = re.findall(pattern_digits, text)
print(f”findall (无分组): {matches_digits}”) # 输出: findall (无分组): [’10’, ’50’, ‘5’]
pattern_price = r”(\d+.\d+)” # 一个捕获组
matches_price = re.findall(pattern_price, text)
print(f”findall (单个分组): {matches_price}”) # 输出: findall (单个分组): [‘10.50’] (返回捕获组内容列表)
pattern_structured = r”(\w+):\s(\d+.?\d)” # 多个捕获组 (属性: 值)
text_structured = “Width: 100, Height: 200.5, Depth: 50″
matches_structured = re.findall(pattern_structured, text_structured)
print(f”findall (多个分组): {matches_structured}”) # 输出: findall (多个分组): [(‘Width’, ‘100’), (‘Height’, ‘200.5’), (‘Depth’, ’50’)] (返回元组列表)
“`
re.finditer(pattern, string, flags=0)
查找 string
中所有与 pattern
匹配的非重叠子串,并返回一个迭代器。迭代器中的每个元素都是一个 Match Object。这对于需要同时获取匹配内容和位置信息的场景非常有用,特别是当匹配项很多时不占用大量内存。
“`python
text = “Emails: [email protected], [email protected]”
pattern_email = r”\w+@\w+.\w+” # 简化的邮箱模式
for match in re.finditer(pattern_email, text):
print(f”Found email: {match.group()} at {match.span()}”)
输出:
Found email: [email protected] at (8, 28)
Found email: [email protected] at (30, 48)
“`
re.sub(pattern, repl, string, count=0, flags=0)
在 string
中找到所有与 pattern
匹配的子串,并用 repl
替换它们。返回替换后的字符串。
count
指定最多替换的次数,0 表示替换所有匹配项。
repl
可以是一个字符串,其中可以使用 \g<number>
或 \g<name>
来引用捕获组的内容(\1
也等价于 \g<1>
)。repl
也可以是一个函数,函数会被每个匹配项调用,其返回值作为替换字符串。
“`python
text = “Hello world, this is a test.”
pattern_whitespace = r”\s+” # 一个或多个空白字符
replacement_underscore = r”_”
replaced_text = re.sub(pattern_whitespace, replacement_underscore, text)
print(f”替换空白字符: {replaced_text}”) # 输出: 替换空白字符: Hello_world,_this_is_a_test.
text = “Date: 2023-10-27″
pattern_date = r”(\d{4})-(\d{2})-(\d{2})”
将 YYYY-MM-DD 格式转换为 MM/DD/YYYY
replacement_format = r”\2/\3/\1″ # \1, \2, \3 引用第一个、第二个、第三个捕获组
replaced_date = re.sub(pattern_date, replacement_format, text)
print(f”重新格式化日期: {replaced_date}”) # 输出: 重新格式化日期: Date: 10/27/2023
使用 repl 函数
def censor(match):
# 匹配到的内容是 match.group(0)
word = match.group(0)
return “*” * len(word) # 用等数量的星号替换
text = “This is a secret message.”
pattern_word = r”\b\w+\b”
censored_text = re.sub(pattern_word, censor, text)
print(f”单词替换为星号: {censored_text}”) # 输出: 单词替换为星号: * ** * ****.
“`
re.split(pattern, string, maxsplit=0, flags=0)
按 pattern
中匹配到的位置分割 string
,返回一个字符串列表。
maxsplit
指定最大分割次数,0 表示不限制。
如果模式 pattern
中包含捕获组,那么匹配到的分隔符内容也会包含在结果列表中。
“`python
text = “apple,banana;orange grape”
pattern_separator = r”[,;\s]+” # 匹配逗号、分号或一个或多个空白字符
split_list = re.split(pattern_separator, text)
print(f”按分隔符分割: {split_list}”) # 输出: 按分隔符分割: [‘apple’, ‘banana’, ‘orange’, ‘grape’]
text = “text1, text2; text3″
pattern_split_with_group = r”([,;]\s*)” # 匹配逗号或分号,后面跟零个或多个空白字符,并捕获分隔符
split_list_with_group = re.split(pattern_split_with_group, text)
print(f”按分隔符分割 (带分组): {split_list_with_group}”)
输出: 按分隔符分割 (带分组): [‘text1’, ‘, ‘, ‘text2’, ‘; ‘, ‘text3’]
注意结果列表中包含了分隔符本身
“`
re.compile(pattern, flags=0)
编译正则表达式模式为一个正则表达式对象。编译后,可以使用对象的方法(如 search
, match
, findall
, sub
, split
等),这些方法的用法与 re
模块级别的函数相同。
为什么要编译? 如果你需要在同一个程序中多次使用同一个正则表达式模式进行匹配,编译它可以提高效率。re
模块会在内部缓存最近使用的模式,但显式编译可以确保模式只被解析一次。
“`python
不编译,每次调用函数时可能会重复解析模式
def process_log_entry(log_entry):
match = re.search(r”ERROR: (.*)”, log_entry)
if match:
return match.group(1)
return None
编译模式,只解析一次
error_pattern = re.compile(r”ERROR: (.*)”)
def process_log_entry_compiled(log_entry):
match = error_pattern.search(log_entry) # 使用编译后的对象
if match:
return match.group(1)
return None
logs = [“INFO: User login”, “ERROR: File not found”, “WARNING: Low disk space”]
print(“使用非编译模式:”)
for log in logs:
error_info = process_log_entry(log)
if error_info:
print(f”提取到错误: {error_info}”)
print(“\n使用编译模式:”)
for log in logs:
error_info = process_log_entry_compiled(log)
if error_info:
print(f”提取到错误: {error_info}”)
输出与上面相同,但在循环次数多时,编译模式更高效
使用非编译模式:
提取到错误: File not found
使用编译模式:
提取到错误: File not found
``
re
对于只使用一次的模式,直接使用模块的函数即可;对于需要重复使用的模式,推荐使用
re.compile()`。
10. Match Object(匹配对象)
当 re.search()
或 re.match()
找到匹配项时,会返回一个 Match Object。这个对象包含了匹配的所有详细信息。
除了前面用过的 group()
, groups()
方法,还有其他有用的方法:
start([group])
: 返回指定捕获组匹配到的子串在原字符串中的起始索引。默认是 0,表示整个匹配。end([group])
: 返回指定捕获组匹配到的子串在原字符串中的结束索引(不包含)。默认是 0,表示整个匹配。span([group])
: 返回指定捕获组匹配到的子串在原字符串中的起始和结束索引组成的元组(start, end)
。默认是 0。
“`python
text = “Email: [email protected]”
pattern = r”(\w+)@(\w+).(\w+)”
match = re.search(pattern, text)
if match:
print(f”整个匹配: {match.group(0)} (span: {match.span(0)})”) # span(0) == span()
print(f”用户名: {match.group(1)} (span: {match.span(1)})”)
print(f”域名: {match.group(2)} (span: {match.span(2)})”)
print(f”顶级域名: {match.group(3)} (span: {match.span(3)})”)
print(f"整个匹配起始索引: {match.start()}")
print(f"整个匹配结束索引 (不含): {match.end()}")
print(f"域名起始索引: {match.start(2)}")
print(f"域名结束索引 (不含): {match.end(2)}")
输出:
整个匹配: [email protected] (span: (7, 22))
用户名: user (span: (7, 11))
域名: example (span: (12, 19))
顶级域名: com (span: (20, 23))
整个匹配起始索引: 7
整个匹配结束索引 (不含): 22
域名起始索引: 12
域名结束索引 (不含): 19
“`
11. 常用标志(Flags)
标志用于修改正则表达式的匹配行为。它们作为 re
模块函数的最后一个参数传入,可以组合使用(使用按位或 |
)。
-
re.IGNORECASE
或re.I
: 进行不区分大小写的匹配。
“`python
text = “Apple apple APPLE”
pattern = r”apple”
matches_case_sensitive = re.findall(pattern, text)
matches_case_insensitive = re.findall(pattern, text, re.IGNORECASE)print(f”区分大小写: {matches_case_sensitive}”) # 输出: 区分大小写: [‘apple’]
print(f”不区分大小写: {matches_case_insensitive}”) # 输出: 不区分大小写: [‘Apple’, ‘apple’, ‘APPLE’]
“` -
re.MULTILINE
或re.M
: 多行模式。影响^
和$
的行为。在多行模式下,^
匹配字符串的开头或每一行的开头(紧跟在\n
之后),$
匹配字符串的结尾或每一行的结尾(紧挨在\n
之前)。
“`python
text = “Line 1\nLine 2\nLine 3″
pattern_start_end_line = r”^Line \d$” # 匹配以 “Line 数字” 开头并结尾的行默认模式 (只匹配字符串开头和结尾)
matches_default = re.findall(pattern_start_end_line, text)
print(f”默认模式 (^$ 匹配字符串): {matches_default}”) # 输出: 默认模式 (^$ 匹配字符串): [] (没有一行同时是字符串开头和结尾)多行模式
matches_multiline = re.findall(pattern_start_end_line, text, re.MULTILINE)
print(f”多行模式 (^$ 匹配行): {matches_multiline}”) # 输出: 多行模式 (^$ 匹配行): [‘Line 1’, ‘Line 2’, ‘Line 3’]
“` -
re.DOTALL
或re.S
: 点号模式。影响.
的行为。在点号模式下,.*
会匹配包括换行符在内的所有字符,而不是默认的不包含换行符。
“`python
text = “First line\nSecond line”
pattern_dot = r”First.Second” # . 不匹配换行符
pattern_dotall = r”First.Second” # . 匹配换行符match_dot = re.search(pattern_dot, text)
if match_dot:
print(f”默认 ‘.’ 匹配: {match_dot.group()}”)
else:
print(“默认 ‘.’ 未匹配 (跨行).”) # 输出: 默认 ‘.’ 未匹配 (跨行).match_dotall = re.search(pattern_dotall, text, re.DOTALL)
if match_dotall:
print(f”DOTALL ‘.’ 匹配: {match_dotall.group()}”) # 输出: DOTALL ‘.’ 匹配: First line\nSecond line
else:
print(“DOTALL ‘.’ 未匹配.”)
“`
其他常用标志还有 re.VERBOSE
(或 re.X
) 允许在模式中添加空白和注释,方便阅读;re.ASCII
(或 re.A
) 使 \w
, \b
, \s
, \d
等只匹配 ASCII 字符,而不是完整的 Unicode 字符。
12. 实践建议与总结
- 始终使用原始字符串 (r”…”) 来定义你的正则表达式模式,避免 Python 字符串转义的困扰。
- 对于需要重复使用的模式,使用
re.compile()
进行编译,可以提高效率。 - 理解
re.search()
和re.match()
的区别:search
扫描整个字符串,match
只从开头匹配。 - 理解
re.findall()
和re.finditer()
的区别:findall
返回列表,finditer
返回迭代器(特别是处理大量匹配时更高效);它们的返回内容在模式包含捕获组时有所不同。 - 利用 Match Object 的方法(
group()
,groups()
,span()
,start()
,end()
) 提取匹配到的详细信息。 - 根据需要使用标志(
re.I
,re.M
,re.S
等)改变匹配行为。 - 在编写复杂正则表达式时,可以借助在线正则表达式测试工具(如 regex101.com 或 regexper.com 可视化工具)来辅助调试和理解。
- 正则表达式是一门独立的“小语言”,掌握它需要时间和练习。从简单的模式开始,逐步构建复杂的模式。
正则表达式是处理文本数据的利器。虽然初看语法可能有些晦涩,但一旦掌握了基本原理和常用元素,你会发现它能极大地提升你的文本处理能力。本指南覆盖了 Python 正则表达式的主要方面,希望为你打开这扇强大的大门。多加实践,你将能灵活运用正则表达式解决各种文本处理问题。
祝你学习顺利!