Python 正则表达式入门:从零开始掌握强大的文本匹配工具
在处理字符串数据时,我们经常需要执行一些复杂的模式匹配、查找、替换或分割操作。例如,从一段文本中提取所有电话号码、验证用户输入的邮箱格式、或者查找所有以特定前缀开头的单词。手动编写代码来处理这些需求通常非常繁琐且容易出错。这时,正则表达式(Regular Expression,简称 Regex 或 RE)就派上了用场。
正则表达式是一种强大而灵活的文本模式匹配工具,它用简洁的符号描述了字符串的模式。Python 内建的 re
模块提供了对正则表达式的完整支持,使得在 Python 中进行复杂的字符串处理变得轻而易举。
本文将带你从零开始,逐步深入了解 Python 中正则表达式的使用,包括基本的语法、re
模块的核心函数以及如何处理匹配结果。
什么是正则表达式?
简单来说,正则表达式就是用来描述一组字符串特征的模式。它由一系列特殊字符和普通字符组成,这些字符组合起来定义了一个搜索模式。你可以把它想象成一个高级的通配符系统,但其能力远超通配符。
例如,如果你想匹配所有以字母 a
开头,后面跟着任意多个字母或数字的字符串,用正则表达式可以简洁地表示出来。
为什么在 Python 中使用正则表达式?
Python 的字符串处理能力本身就很强大,提供了 startswith()
, endswith()
, find()
, replace()
, split()
等方法。但当需求变得复杂时,比如:
- 查找所有符合特定格式(如
XXX-XXXX-XXXX
)的电话号码。 - 从网页 HTML 中提取所有 URL 链接。
- 验证用户输入的密码是否包含大小写字母、数字和特殊字符。
- 从日志文件中过滤出特定错误信息。
这时,使用正则表达式会比使用 Python 内建字符串方法组合来实现要高效得多,代码也更简洁、更易读(一旦你熟悉了正则表达式的语法)。Python 的 re
模块提供了与 Perl 语言类似的正则表达式功能,非常成熟和稳定。
Python 的 re
模块
Python 使用标准的 re
模块来支持正则表达式。要使用正则表达式,首先需要导入这个模块:
python
import re
re
模块提供了一系列函数,用于执行正则表达式操作,如查找、匹配、替换、分割等。这些函数通常接收两个主要参数:
- Pattern (模式): 一个字符串,表示要匹配的正则表达式。
- String (字符串): 要在其中进行匹配操作的目标字符串。
原始字符串 (Raw Strings) 的重要性
在 Python 中书写正则表达式模式时,强烈推荐使用原始字符串(Raw Strings)。原始字符串以 r
或 R
开头,例如 r"your regex pattern"
。
为什么需要原始字符串?因为正则表达式中大量使用了反斜杠 \
作为转义字符(例如 \d
表示数字,\s
表示空白字符)。而 Python 字符串本身也使用反斜杠作为转义字符(例如 \n
表示换行,\t
表示制表符)。如果不使用原始字符串,你就需要对模式中的每个反斜杠都进行双重转义,比如 \\d
来表示正则表达式中的 \d
。这会让模式变得非常难以阅读和书写。
使用原始字符串 r"\d+"
,Python 解释器会将其中的反斜杠视为字面量,直接传递给 re
模块,避免了 Python 自身的转义干扰。
“`python
不使用原始字符串,需要双重转义
pattern_without_raw = “\d+”
print(pattern_without_raw) # 输出:\d+ (Python内部表示)
使用原始字符串,推荐方式
pattern_with_raw = r”\d+”
print(pattern_with_raw) # 输出:\d+ (直接表示正则表达式中的 \d+)
“`
因此,记住:编写正则表达式模式时,总是使用原始字符串 r""
。
正则表达式基础语法 (Patterns)
正则表达式的强大之处在于其模式语法。掌握这些基本语法是使用正则表达式的关键。
1. 字面字符 (Literal Characters)
大多数字符(如字母、数字、符号)在正则表达式中都表示它们本身的含义。例如,模式 cat
会精确匹配字符串中的子串 “cat”。
“`python
import re
text = “The cat sat on the mat.”
pattern = r”cat”
match = re.search(pattern, text)
if match:
print(f”找到匹配: {match.group()}”) # 输出:找到匹配: cat
“`
2. 元字符 (Metacharacters)
元字符是正则表达式中具有特殊含义的字符,它们不是匹配字符本身,而是描述匹配的规则。这是正则表达式灵活性的核心。
-
. (点号): 匹配除了换行符
\n
之外的任意单个字符。“`python
text = “hat hot hit”
pattern = r”h.t” # 匹配 h 后面跟任意一个字符,再跟 tmatches = re.findall(pattern, text)
print(matches) # 输出:[‘hat’, ‘hot’, ‘hit’]
“` -
^
(脱字符): 匹配字符串的开头。“`python
text1 = “Hello world”
text2 = “world Hello”
pattern = r”^Hello” # 匹配以 Hello 开头的字符串match1 = re.search(pattern, text1)
match2 = re.search(pattern, text2)print(f”‘{text1}’ 开头是 ‘Hello’吗? {bool(match1)}”) # 输出:’Hello world’ 开头是 ‘Hello’吗? True
print(f”‘{text2}’ 开头是 ‘Hello’吗? {bool(match2)}”) # 输出:’world Hello’ 开头是 ‘Hello’吗? False
“` -
$
(美元符号): 匹配字符串的结尾。“`python
text1 = “Hello world”
text2 = “world Hello”
pattern = r”world$” # 匹配以 world 结尾的字符串match1 = re.search(pattern, text1)
match2 = re.search(pattern, text2)print(f”‘{text1}’ 结尾是 ‘world’吗? {bool(match1)}”) # 输出:’Hello world’ 结尾是 ‘world’吗? True
print(f”‘{text2}’ 结尾是 ‘world’吗? {bool(match2)}”) # 输出:’world Hello’ 结尾是 ‘world’吗? False
“` -
*
(星号): 匹配前一个字符零次或多次。“`python
text = “go goo goooogle”
pattern = r”go*” # 匹配 g 后面跟着零个或多个 omatches = re.findall(pattern, text)
print(matches) # 输出:[‘g’, ‘goo’, ‘goooo’]
“` -
+
(加号): 匹配前一个字符一次或多次。“`python
text = “go goo goooogle”
pattern = r”go+” # 匹配 g 后面跟着一个或多个 omatches = re.findall(pattern, text)
print(matches) # 输出:[‘goo’, ‘goooo’]
“` -
?
(问号): 匹配前一个字符零次或一次。“`python
text = “color colour”
pattern = r”colou?r” # 匹配 colou 后面跟着零个或一个 u,再跟着 rmatches = re.findall(pattern, text)
print(matches) # 输出:[‘color’, ‘colour’]
“`
3. 字符集/字符类 (Character Sets/Classes) []
方括号 []
定义了一个字符集合。它匹配方括号中列出的任意一个字符。
[abc]
匹配 ‘a’, ‘b’, 或 ‘c’ 中的任意一个。[0-9]
匹配任意一个数字(等同于\d
)。[a-z]
匹配任意一个小写字母。[A-Z]
匹配任意一个大写字母。[a-zA-Z]
匹配任意一个字母。[a-zA-Z0-9]
匹配任意一个字母或数字(等同于\w
的一部分)。
在 []
内部,大部分元字符会失去特殊含义(除了 ^
在开头表示否定,-
在中间表示范围,\
进行转义,以及 ]
本身需要转义 \]
)。
“`python
text = “cat hat bat sat”
pattern = r”[chb]at” # 匹配 c, h, 或 b 中的任意一个,后面跟着 at
matches = re.findall(pattern, text)
print(matches) # 输出:[‘cat’, ‘hat’, ‘bat’]
“`
4. 否定字符集 [^]
在 []
内部的开头使用 ^
表示匹配不在该集合中的任意一个字符。
“`python
text = “cat mat bat fat 1at”
pattern = r”[^cmf]at” # 匹配不是 c, m, 或 f 的任意一个字符,后面跟着 at
matches = re.findall(pattern, text)
print(matches) # 输出:[‘bat’, ‘1at’]
“`
5. 预定义字符类 (Predefined Character Classes)
re
模块提供了一些常用的预定义字符类,它们是常用字符集的简写形式,更方便。
\d
: 匹配任意一个数字 (0-9)。等同于[0-9]
。\D
: 匹配任意一个非数字字符。等同于[^0-9]
。\w
: 匹配任意一个字母、数字或下划线。在 ASCII 模式下等同于[a-zA-Z0-9_]
。\W
: 匹配任意一个非字母、数字或下划线字符。等同于[^\w]
。\s
: 匹配任意一个空白字符(空格、制表符\t
、换行符\n
、回车符\r
、换页符\f
等)。\S
: 匹配任意一个非空白字符。等同于[^\s]
。
“`python
text = “Phone number: 123-456-7890, Email: [email protected]”
pattern_number = r”\d+” # 匹配一个或多个数字
pattern_word = r”\w+” # 匹配一个或多个字母、数字或下划线
pattern_whitespace = r”\s+” # 匹配一个或多个空白字符
numbers = re.findall(pattern_number, text)
words = re.findall(pattern_word, text)
whitespaces = re.findall(pattern_whitespace, text)
print(f”Numbers: {numbers}”) # 输出:Numbers: [‘123’, ‘456’, ‘7890’]
print(f”Words: {words}”) # 输出:Words: [‘Phone’, ‘number’, ‘123’, ‘456’, ‘7890’, ‘Email’, ‘test’, ‘example’, ‘com’]
print(f”Whitespaces: {whitespaces}”) # 输出:Whitespaces: [‘ ‘, ‘: ‘, ‘-‘, ‘-‘, ‘, ‘, ‘ ‘, ‘: ‘, ‘@’, ‘.’]
“`
6. 量词 (Quantifiers)
量词用于指定前一个字符、字符类或分组应该出现多少次。
{n}
: 匹配前一个元素恰好出现 n 次。{n,}
: 匹配前一个元素至少出现 n 次。{,m}
: 匹配前一个元素最多出现 m 次。{n,m}
: 匹配前一个元素出现 n 到 m 次(包含 n 和 m)。
注意: *
等同于 {0,}
, +
等同于 {1,}
, ?
等同于 {0,1}
。
默认情况下,量词是“贪婪”的(Greedy),会尽可能多地匹配字符。在量词后加上 ?
可以使其变为“非贪婪”或“惰性”的(Non-greedy/Lazy),会尽可能少地匹配字符。
“`python
text = “aaaaa”
pattern_greedy = r”a{2,4}” # 匹配 2 到 4 个 a (贪婪)
pattern_lazy = r”a{2,4}?” # 匹配 2 到 4 个 a (非贪婪)
match_greedy = re.search(pattern_greedy, text)
match_lazy = re.search(pattern_lazy, text)
print(f”Greedy match: {match_greedy.group()}”) # 输出:Greedy match: aaaa
print(f”Lazy match: {match_lazy.group()}”) # 输出:Lazy match: aa
“`
7. 边界匹配 (Anchors)
除了 ^
和 $
,还有其他用于匹配特定位置的元字符。
\b
: 匹配一个单词边界。单词边界是指一个\w
字符和一个\W
字符之间的位置,或者\w
字符与字符串开头/结尾之间的位置。它匹配的是位置,而不是字符本身。\B
: 匹配一个非单词边界。
“`python
text = “cat catalog concatenate the scattered cats”
pattern_word = r”\bcat\b” # 匹配完整的单词 “cat”
pattern_not_word = r”\Bcat\B” # 匹配前后都不是单词边界的 “cat”
matches_word = re.findall(pattern_word, text)
matches_not_word = re.findall(pattern_not_word, text)
print(f”Full word ‘cat’: {matches_word}”) # 输出:Full word ‘cat’: [‘cat’, ‘cats’] (注意 cats 结尾是边界)
print(f”Non-word boundary ‘cat’: {matches_not_word}”) # 输出:Non-word boundary ‘cat’: [‘cat’] (在 concatenate 中)
“`
8. 分组 (Grouping) ()
圆括号 ()
用于将一个或多个字符组合成一个逻辑单元,可以对整个组应用量词,或者用于捕获匹配的子串(后面会讲到)。
“`python
text = “ababab”
pattern = r”(ab)+” # 匹配一个或多个 “ab” 组合
matches = re.findall(pattern, text)
print(matches) # 输出:[‘ab’] – 注意 findall 在有分组时只返回捕获组的内容
``
findall
要获取完整的匹配,需要结合使用或使用其他函数,或者理解在有无分组时的区别。对于
findall`,如果没有分组,它返回所有完全匹配的子串;如果有分组,它返回所有匹配的捕获组的内容(如果只有一个分组,返回列表,如果有多个分组,返回元组列表)。
9. 候选项 (Alternation) |
竖线 |
用于表示“或”的关系,匹配 |
符号左边或右边的模式。通常与分组 ()
一起使用。
“`python
text = “apple banana orange”
pattern = r”(apple|banana)” # 匹配 “apple” 或 “banana”
matches = re.findall(pattern, text)
print(matches) # 输出:[‘apple’, ‘banana’]
“`
10. 转义特殊字符 \
如果想要匹配元字符本身(如 .
*
+
?
^
$
()
[]
{}
|
\
),需要在前面加上反斜杠 \
进行转义。
“`python
text = “Need 10$ for the game.”
pattern = r”10\$” # 匹配字面量 “10$”
match = re.search(pattern, text)
if match:
print(f”找到匹配: {match.group()}”) # 输出:找到匹配: 10$
“`
在使用原始字符串时,你只需要写一个反斜杠来转义元字符。
re
模块的核心函数
了解了正则表达式的模式语法后,我们来看看如何在 Python 中使用 re
模块的函数来执行匹配操作。
1. re.search(pattern, string, flags=0)
扫描整个字符串,查找第一个与模式匹配的位置。如果找到,返回一个Match Object (匹配对象);如果没有找到,返回 None
。
匹配对象包含了匹配的详细信息。
“`python
import re
text = “The quick brown fox jumps over the lazy dog.”
pattern = r”fox”
match = re.search(pattern, text)
if match:
print(“找到匹配!”)
print(f”匹配的起始位置: {match.start()}”)
print(f”匹配的结束位置: {match.end()}”)
print(f”匹配的子串: {match.group()}”)
else:
print(“未找到匹配。”)
输出:
找到匹配!
匹配的起始位置: 16
匹配的结束位置: 19
匹配的子串: fox
“`
2. re.match(pattern, string, flags=0)
尝试从字符串的开头匹配模式。如果字符串的开头符合模式,返回一个 Match Object;否则,返回 None
。
match()
和 search()
的主要区别在于 match()
只从字符串的起始位置开始匹配,而 search()
会扫描整个字符串直到找到第一个匹配。
“`python
import re
text1 = “Hello world”
text2 = “world Hello”
pattern = r”Hello”
match1 = re.match(pattern, text1)
match2 = re.match(pattern, text2)
print(f”re.match(‘{pattern}’, ‘{text1}’): {bool(match1)}”) # 输出:re.match(‘Hello’, ‘Hello world’): True
print(f”re.match(‘{pattern}’, ‘{text2}’): {bool(match2)}”) # 输出:re.match(‘Hello’, ‘world Hello’): False
print(f”re.search(‘{pattern}’, ‘{text2}’): {bool(re.search(pattern, text2))}”) # 输出:re.search(‘Hello’, ‘world Hello’): True
“`
3. re.findall(pattern, string, flags=0)
在字符串中查找所有与模式匹配的非重叠子串,并以一个列表的形式返回。
- 如果模式中没有捕获组(即没有使用
()
分组),返回所有完全匹配的子串列表。 - 如果模式中有一个捕获组,返回该捕获组匹配到的所有内容的列表。
- 如果模式中有多个捕获组,返回一个元组列表,每个元组包含一个匹配中所有捕获组的内容。
“`python
import re
text = “The numbers are 123, 456 and 789.”
pattern_no_group = r”\d+”
pattern_with_group = r”(\d+)” # 使用了捕获组
matches_no_group = re.findall(pattern_no_group, text)
matches_with_group = re.findall(pattern_with_group, text)
print(f”findall (no group): {matches_no_group}”) # 输出:findall (no group): [‘123’, ‘456’, ‘789’]
print(f”findall (with group): {matches_with_group}”) # 输出:findall (with group): [‘123’, ‘456’, ‘789’]
text_dates = “Date: 2023-10-26, Another: 2024-01-15″
pattern_multi_group = r”(\d{4})-(\d{2})-(\d{2})”
matches_multi_group = re.findall(pattern_multi_group, text_dates)
print(f”findall (multi group): {matches_multi_group}”)
输出:findall (multi group): [(‘2023′, ’10’, ’26’), (‘2024′, ’01’, ’15’)]
“`
4. re.finditer(pattern, string, flags=0)
在字符串中查找所有与模式匹配的非重叠子串,并返回一个迭代器,该迭代器生成 Match Object。当需要获取每个匹配的详细信息(如位置、捕获组内容)时,finditer()
比 findall()
更有用。
“`python
import re
text = “Emails: [email protected], [email protected]”
pattern = r”(\w+)@(\w+.\w+)” # 捕获用户名和域名
for match in re.finditer(pattern, text):
print(f”完整匹配: {match.group()}”)
print(f”用户名: {match.group(1)}”) # group(1) 获取第一个捕获组的内容
print(f”域名: {match.group(2)}”) # group(2) 获取第二个捕获组的内容
print(f”匹配范围: {match.span()}”)
print(“-” * 10)
输出:
完整匹配: [email protected]
用户名: test1
域名: example.com
匹配范围: (8, 27)
———-
完整匹配: [email protected]
用户名: test2
域名: domain.org
匹配范围: (30, 48)
“`
5. re.sub(pattern, repl, string, count=0, flags=0)
在字符串中查找所有与模式匹配的子串,并用指定的替换字符串 repl
替换它们。返回替换后的新字符串。
repl
可以是一个字符串,也可以是一个函数。count
参数指定最多替换的次数,默认为 0,表示替换所有匹配。
如果 repl
是一个字符串,可以使用 \g<number>
或 \g<name>
(对于命名分组)来引用捕获组的内容。
“`python
import re
text = “Date: 2023-10-26”
将 YYYY-MM-DD 格式转换为 MM/DD/YYYY
pattern = r”(\d{4})-(\d{2})-(\d{2})”
\g<2> 引用第二个捕获组(月份),\g<3> 引用第三个(日期),\g<1> 引用第一个(年份)
replacement = r”\g<2>/\g<3>/\g<1>”
new_text = re.sub(pattern, replacement, text)
print(f”Original: {text}”)
print(f”Replaced: {new_text}”)
输出:
Original: Date: 2023-10-26
Replaced: Date: 10/26/2023
“`
如果 repl
是一个函数,该函数会接收一个 Match Object 作为参数,并返回用于替换的字符串。这提供了更灵活的替换逻辑。
“`python
import re
text = “Numbers: 10, 25, 5″
pattern = r”\d+”
定义一个替换函数,将数字乘以2
def double_number(match):
number = int(match.group(0)) # match.group(0) 或 match.group() 获取整个匹配的子串
return str(number * 2)
new_text = re.sub(pattern, double_number, text)
print(f”Original: {text}”)
print(f”Replaced: {new_text}”)
输出:
Original: Numbers: 10, 25, 5
Replaced: Numbers: 20, 50, 10
“`
6. re.split(pattern, string, maxsplit=0, flags=0)
根据正则表达式模式在字符串中进行分割。返回一个字符串列表。
maxsplit
参数指定最大分割次数,默认为 0,表示所有匹配位置都进行分割。- 如果模式中有捕获组,那么捕获组匹配到的内容也会包含在结果列表中。
“`python
import re
text = “apple,banana;orange grape”
pattern_delimiters = r”[;, ]” # 匹配逗号、分号或空格作为分隔符
parts = re.split(pattern_delimiters, text)
print(f”Split by [,; ]: {parts}”) # 输出:Split by [,; ]: [‘apple’, ‘banana’, ‘orange’, ‘grape’]
如果分隔符有捕获组
pattern_delimiters_with_group = r”([;, ])”
parts_with_group = re.split(pattern_delimiters_with_group, text)
print(f”Split by ([;, ]): {parts_with_group}”)
输出:Split by ([;, ]): [‘apple’, ‘,’, ‘banana’, ‘;’, ‘orange’, ‘ ‘, ‘grape’, ”]
“`
7. re.compile(pattern, flags=0)
将正则表达式模式编译成一个正则表达式对象。编译后的对象具有与 re
模块函数同名的方法(如 search
, match
, findall
, sub
, split
等)。
编译正则表达式有两个主要好处:
- 效率: 如果一个正则表达式需要在同一个程序中被多次使用,编译它可以显著提高效率,因为编译过程只需要执行一次。
- 可读性: 将正则表达式对象存储在一个变量中,可以使代码更清晰。
“`python
import re
编译正则表达式对象
email_pattern = re.compile(r”(\w+)@(\w+.\w+)”)
text1 = “Contact: [email protected]”
text2 = “Reply to: [email protected]”
使用编译后的对象的方法
match1 = email_pattern.search(text1)
match2 = email_pattern.search(text2)
if match1:
print(f”Text 1 match: {match1.group()}”)
if match2:
print(f”Text 2 match: {match2.group()}”)
输出:
Text 1 match: [email protected]
Text 2 match: [email protected]
“`
匹配对象 (Match Object)
当 re.search()
或 re.match()
找到匹配时,它们会返回一个 Match Object。这个对象包含了匹配的详细信息,可以通过其方法来访问:
match.group(0)
或match.group()
: 返回整个匹配的子串。match.group(n)
: 返回第 n 个捕获组匹配到的子串(从 1 开始计数)。match.groups()
: 返回一个元组,包含所有捕获组匹配到的子串。match.start(n)
: 返回第 n 个捕获组(或整个匹配,如果 n=0)的起始索引。match.end(n)
: 返回第 n 个捕获组(或整个匹配)的结束索引(不包含该位置)。match.span(n)
: 返回第 n 个捕获组(或整个匹配)的起始和结束索引组成的元组(start, end)
。
“`python
import re
text = “My number is 123-456-7890.”
pattern = r”(\d{3})-(\d{3})-(\d{4})” # 3个捕获组
match = re.search(pattern, text)
if match:
print(f”完整匹配: {match.group()}”) # 输出:完整匹配: 123-456-7890
print(f”第一个分组: {match.group(1)}”) # 输出:第一个分组: 123
print(f”第二个分组: {match.group(2)}”) # 输出:第二个分组: 456
print(f”第三个分组: {match.group(3)}”) # 输出:第三个分组: 7890
print(f”所有分组: {match.groups()}”) # 输出:所有分组: (‘123’, ‘456’, ‘7890’)
print(f”完整匹配的起始结束索引: {match.span()}”) # 输出:完整匹配的起始结束索引: (13, 25)
print(f”第一个分组的起始结束索引: {match.span(1)}”) # 输出:第一个分组的起始结束索引: (13, 16)
“`
正则表达式标志 (Flags)
re
模块的函数和 re.compile()
都接受一个可选的 flags
参数,用于修改匹配行为。常用的标志包括:
re.IGNORECASE
或re.I
: 忽略大小写匹配。re.MULTILINE
或re.M
: 多行模式。^
和$
不仅匹配整个字符串的开头和结尾,还匹配每一行的开头和结尾(由换行符分隔)。re.DOTALL
或re.S
: 点号.
匹配包括换行符\n
在内的任意字符。re.VERBOSE
或re.X
: 详细模式。忽略模式字符串中的空白字符和#
后面的注释,使复杂的正则表达式更易读。
可以通过位运算符 |
来组合多个标志。
“`python
import re
text = “Hello World\nhello python”
忽略大小写
match_ignorecase = re.search(r”^hello”, text, re.IGNORECASE)
print(f”忽略大小写匹配开头: {bool(match_ignorecase)}”) # 输出:忽略大小写匹配开头: True
多行模式
match_multiline = re.search(r”^hello”, text, re.MULTILINE) # 匹配每一行的开头
print(f”多行模式匹配第二个 ‘hello’: {bool(match_multiline)}”) # 输出:多行模式匹配第二个 ‘hello’: True
点号匹配换行符
text_multiline = “Line1\nLine2″
match_dot_no_flag = re.search(r”Line.\nLine.”, text_multiline) # 默认点号不匹配换行
match_dot_flag = re.search(r”Line.\nLine.”, text_multiline, re.DOTALL) # 点号匹配换行
print(f”默认点号匹配换行符? {bool(match_dot_no_flag)}”) # 输出:默认点号匹配换行符? False
print(f”DOTALL点号匹配换行符? {bool(match_dot_flag)}”) # 输出:DOTALL点号匹配换行符? True
详细模式
pattern_verbose = r”””
^(\d{4}) # 匹配年份
– # 匹配短横线
(\d{2}) # 匹配月份
– # 匹配短横线
(\d{2}) # 匹配日期
$ # 匹配结尾
“””
text_date = “2023-10-26″
match_verbose = re.search(pattern_verbose, text_date, re.VERBOSE)
print(f”详细模式匹配日期: {bool(match_verbose)}”) # 输出:详细模式匹配日期: True
“`
更多高级概念(初学者了解即可)
- 非捕获分组
(?:...)
: 使用(?:...)
可以分组但不创建捕获组,这在只需要分组进行量词限定或使用|
时很有用,可以提高一点效率并避免在groups()
中出现不必要的元素。 - 前向断言和后向断言 (
(?=...)
,(?!...)
,(?<=...)
,(?<!...)
): 这些是零宽度的断言,它们匹配一个位置,该位置后面或前面跟着(或不跟着)特定的模式,但它们本身不消耗字符。(?=...)
前向肯定断言:要求当前位置后面必须匹配...
。(?!...)
前向否定断言:要求当前位置后面不能匹配...
。(?<=...)
后向肯定断言:要求当前位置前面必须匹配...
。(?<!...)
后向否定断言:要求当前位置前面不能匹配...
。
- 命名分组
(?P<name>...)
: 使用(?P<name>...)
可以给捕获组命名,通过match.group('name')
或match.group(name)
来访问,增强代码可读性。在re.sub
的替换字符串中可以使用\g<name>
引用。
“`python
import re
text = “cat dog cat bat”
查找后面跟着空格或边界的 “cat”
pattern_lookahead = r”cat(?=\s|\b)”
matches_lookahead = re.findall(pattern_lookahead, text)
print(f”前向肯定断言示例: {matches_lookahead}”) # 输出:前向肯定断言示例: [‘cat’, ‘cat’]
使用命名分组
text_date = “Date is 2023-10-26”
pattern_named_group = r”(?P
match_named = re.search(pattern_named_group, text_date)
if match_named:
print(f”命名分组示例: 年={match_named.group(‘year’)}, 月={match_named.group(‘month’)}, 日={match_named.group(‘day’)}”)
# 输出:命名分组示例: 年=2023, 月=10, 日=26
# re.sub中使用命名分组
replacement_named = r"月\g<month>日\g<day>年\g<year>"
new_text_named = re.sub(pattern_named_group, replacement_named, text_date)
print(f"使用命名分组替换: {new_text_named}")
# 输出:使用命名分组替换: Date is 月10日26年2023
“`
实践中的建议
- 总是使用原始字符串
r""
。 - 对于重复使用的模式,考虑使用
re.compile()
。 - 理解
search()
和match()
的区别。 大多数情况下你可能需要的是search()
。 - 理解
findall()
在有无分组时的不同行为。 如果需要详细匹配信息,使用finditer()
。 - 从小处着手构建模式。 先匹配简单的部分,然后逐步添加更复杂的规则。
- 使用在线正则表达式测试工具。 这能极大地帮助你构建和调试复杂的模式。例如 regex101.com 或 regexr.com。
- 注释复杂的模式。 对于复杂的模式,使用
re.VERBOSE
标志并在模式中添加注释可以显著提高可读性。
总结
正则表达式是一个强大而富有表现力的工具,用于处理各种字符串匹配和操作任务。Python 的 re
模块提供了完整的功能支持。通过本文的学习,你应该已经掌握了正则表达式的基础语法,包括字面字符、元字符、字符集、量词、边界以及分组等,并了解了 re
模块中常用的函数:search()
, match()
, findall()
, finditer()
, sub()
, split()
以及 compile()
。同时,你也了解了如何处理匹配对象来获取详细信息,以及如何使用标志来修改匹配行为。
学习正则表达式需要时间和实践。最好的方法是多写、多练、多尝试。从简单的模式开始,逐步挑战更复杂的匹配问题。随着你对正则表达式语法的熟悉,你会发现它在文本处理领域能发挥巨大的作用,极大地提高你的编程效率。
现在,开始你的正则表达式之旅吧!