Python 正则表达式精讲:常用函数与实战技巧325


在日常的编程工作中,文本处理是一个不可避免的环节。无论是数据清洗、日志分析、爬虫数据提取,还是用户输入验证,正则表达式(Regular Expression,简称 Regex 或 Regexp)都是一个极其强大且高效的工具。Python 通过内置的 re 模块提供了对正则表达式的全面支持。本文将作为一份专业的指南,深入探讨 Python 中正则表达式的常用代码、核心函数及其在实际场景中的应用。

一、初识 Python `re` 模块与正则表达式

Python 中使用正则表达式需要导入标准库 re。正则表达式本身是一种强大的字符串模式匹配语言,它定义了一系列特殊字符和语法规则,用于描述字符串的模式。在 Python 中,我们通常使用原始字符串(raw string,前缀为 r)来定义正则表达式,以避免反斜杠 \ 被 Python 自身解释为转义字符,从而造成混淆。例如:r'' 表示匹配换行符,而不是 Python 字符串中的换行符。import re
# 使用原始字符串定义正则表达式,推荐做法
pattern = r"hello\s+world"
print(pattern)

二、正则表达式基础语法回顾

掌握 Python `re` 模块之前,对正则表达式的基本语法有一定了解是前提。以下是一些最常用的元字符和语法规则:
字符匹配:

.:匹配除换行符以外的任意字符。
\d:匹配任何数字(等价于 [0-9])。
\D:匹配任何非数字字符。
\w:匹配任何字母、数字或下划线(等价于 [a-zA-Z0-9_])。
\W:匹配任何非字母、数字或下划线字符。
\s:匹配任何空白字符(空格、制表符、换行符等)。
\S:匹配任何非空白字符。
[xyz]:匹配方括号内的任意一个字符。
[^xyz]:匹配除方括号内字符以外的任意字符。


重复匹配(量词):

*:匹配前一个字符零次或多次。
+:匹配前一个字符一次或多次。
?:匹配前一个字符零次或一次。
{n}:匹配前一个字符恰好 n 次。
{n,}:匹配前一个字符至少 n 次。
{n,m}:匹配前一个字符 n 到 m 次。
贪婪与非贪婪: 默认量词是贪婪的,会尽可能多地匹配。在量词后添加 ? 可以使其变为非贪婪匹配,尽可能少地匹配。例如:.*?, .+?。


位置匹配(锚点):

^:匹配字符串的开始。
$:匹配字符串的结束。
\b:匹配单词的边界。
\B:匹配非单词边界。


分组与或:

(pattern):捕获分组,匹配括号内的模式,并捕获匹配到的文本。
(?:pattern):非捕获分组,只匹配模式,不捕获文本。
|:逻辑或,匹配 | 符号左边或右边的表达式。



三、`re` 模块核心函数与常用代码示例

re 模块提供了多个函数用于执行正则表达式操作。以下是最常用的一些:

1. `()`:扫描整个字符串,找到第一个匹配项


(pattern, string, flags=0) 函数会扫描整个字符串,找到第一个匹配 pattern 的位置。如果找到,返回一个 Match 对象;否则,返回 None。import re
text = "Hello, my phone number is 138-1234-5678 and another is 139-8765-4321."
phone_pattern = r"\d{3}-\d{4}-\d{4}"
match = (phone_pattern, text)
if match:
print(f"找到匹配项:{(0)}") # group(0) 或 group() 返回整个匹配的字符串
print(f"匹配起始位置:{()}")
print(f"匹配结束位置:{()}")
print(f"匹配的元组:{()}")
else:
print("未找到匹配项。")
# 示例:使用分组捕获特定部分
text_with_names = "Name: Alice Age: 30; Name: Bob Age: 25."
name_age_pattern = r"Name:s*(\w+)\s*Age:s*(\d+)"
match_group = (name_age_pattern, text_with_names)
if match_group:
print(f"完整匹配: {(0)}")
print(f"姓名: {(1)}") # 捕获第一个分组 (姓名)
print(f"年龄: {(2)}") # 捕获第二个分组 (年龄)
print(f"所有分组: {()}") # 返回所有捕获分组的元组

2. `()`:只从字符串的起始位置匹配


(pattern, string, flags=0) 函数尝试从字符串的起始位置匹配 pattern。如果字符串开头匹配成功,返回一个 Match 对象;否则,返回 None。它与 () 的主要区别在于匹配的起始位置。import re
text1 = "Hello Python, World!"
text2 = "Python is great."
pattern = r"Python"
match1 = (pattern, text1) # text1 开头不是 "Python"
match2 = (pattern, text2) # text2 开头是 "Python"
if match1:
print(f"text1 匹配成功:{()}")
else:
print("text1 未在开头匹配 'Python'。")
if match2:
print(f"text2 匹配成功:{()}")
else:
print("text2 未在开头匹配 'Python'。")

3. `()`:查找所有非重叠匹配项


(pattern, string, flags=0) 函数在字符串中查找所有与 pattern 匹配的非重叠子串,并以列表形式返回它们。
如果模式中不包含捕获组,则返回所有匹配到的字符串列表。
如果模式中包含一个捕获组,则返回该捕获组捕获到的字符串列表。
如果模式中包含多个捕获组,则返回一个元组列表,每个元组包含对应捕获组捕获到的字符串。

import re
text = "Emails: test@, user@, info@"
email_pattern = r"\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b"
emails = (email_pattern, text)
print(f"找到所有邮箱地址:{emails}")
# 示例:多个捕获组
log_data = "Error: File not found (code 404). Warning: disk full (code 500)."
error_pattern = r"(Error|Warning):s*([^()]+)\s*\(code\s*(\d+)\)"
matches = (error_pattern, log_data)
print(f"找到所有错误/警告信息:{matches}")
# 输出: [('Error', 'File not found ', '404'), ('Warning', 'disk full ', '500')]

4. `()`:替换匹配到的子串


(pattern, repl, string, count=0, flags=0) 函数用于在字符串中查找所有匹配 pattern 的子串,并将其替换为 repl。count 参数指定最大替换次数,默认为 0(即替换所有匹配项)。repl 可以是字符串,也可以是一个函数。import re
text = "Today is 2023-10-26. Tomorrow will be 2023-10-27."
date_pattern = r"\d{4}-\d{2}-\d{2}"
# 字符串替换
new_text1 = (date_pattern, "XXXX-XX-XX", text)
print(f"替换日期后的字符串1:{new_text1}")
# 限制替换次数
new_text2 = (date_pattern, "[DATE]", text, count=1)
print(f"替换日期后的字符串2 (一次):{new_text2}")
# 使用函数作为 repl,对匹配到的内容进行处理
def format_date(match_obj):
# match_obj 是 对象
date_str = (0)
year, month, day = ('-')
return f"{month}/{day}/{year}"
new_text3 = (date_pattern, format_date, text)
print(f"使用函数替换日期后的字符串:{new_text3}")

5. `()`:根据正则表达式分割字符串


(pattern, string, maxsplit=0, flags=0) 函数使用正则表达式 pattern 作为分隔符来分割字符串 string,并返回一个字符串列表。maxsplit 参数可以指定最大分割次数。import re
text = "one, two;three four"
# 使用逗号、分号或空格作为分隔符
parts = (r"[,;\s]", text)
print(f"分割后的字符串列表:{parts}")
text2 = "apple-banana-orange"
# 限制分割次数
parts2 = (r"-", text2, maxsplit=1)
print(f"限制分割次数的列表:{parts2}")

6. `()`:编译正则表达式以提高效率


当一个正则表达式模式需要被多次使用时,可以使用 () 函数将其编译成一个正则表达式对象。这样可以提高效率,因为 Python 不必每次都重新解析模式。编译后的模式对象也有 search(), match(), findall(), sub(), split() 等方法。import re
import time
# 不编译
start_time = ()
for _ in range(100000):
(r"\d+", "abc123def456")
end_time = ()
print(f"不编译耗时:{end_time - start_time:.4f} 秒")
# 编译模式
compiled_pattern = (r"\d+")
start_time = ()
for _ in range(100000):
("abc123def456")
end_time = ()
print(f"编译后耗时:{end_time - start_time:.4f} 秒")
# 编译后的模式对象使用方法
text = "The quick brown fox jumps over the lazy dog."
vowels = (text) # 这实际上是错误的示例,编译的是数字,文本中没有
print(f"编译模式后在文本中查找数字 (期望为空): {vowels}")
# 更正一个有意义的示例
compiled_word_pattern = (r"\b\w+\b")
words = (text)
print(f"编译模式后查找所有单词: {words}")

四、常用的 `re` 标志 (Flags)

re 模块的函数通常接受一个 flags 参数,用于修改正则表达式的匹配行为。常用的标志有:
(或 re.I):忽略大小写匹配。
(或 re.M):使 ^ 和 $ 匹配每行的开头和结尾,而不仅仅是整个字符串的开头和结尾。
(或 re.S):使 . 匹配包括换行符在内的所有字符。
(或 re.X):允许在正则表达式中添加注释和空白,使其更具可读性。

import re
text = "Hello Worldhello python"
#
match_case = (r"hello", text)
print(f"大小写敏感匹配: {() if match_case else '无'}") # Output: None
match_nocase = (r"hello", text, )
print(f"大小写不敏感匹配: {() if match_nocase else '无'}") # Output: Hello
#
multi_line_text = "Line 1Line 2Line 3"
starts_with_line = (r"^Line", multi_line_text)
print(f"默认模式下匹配行首: {starts_with_line}") # Output: ['Line']
starts_with_line_m = (r"^Line", multi_line_text, )
print(f"多行模式下匹配行首: {starts_with_line_m}") # Output: ['Line', 'Line', 'Line']
#
dot_text = "Line 1Line 2"
dot_match_default = (r"Line.*Line", dot_text)
print(f"默认点号匹配 (不包括换行): {dot_match_default}") # Output: None
dot_match_all = (r"Line.*Line", dot_text, )
print(f"点号匹配所有字符 (包括换行): {()}") # Output: Line 1Line 2
# (通常用于复杂模式,提升可读性)
long_pattern = (r"""
^(\d{3}) # 匹配三位数字开头的区号
\s* # 匹配零个或多个空格
-? # 匹配零个或一个连字符
\s* # 匹配零个或多个空格
(\d{4}) # 匹配四位数字的号码前缀
\s* # 匹配零个或多个空格
-? # 匹配零个或一个连字符
\s* # 匹配零个或多个空格
(\d{4})$ # 匹配四位数字的号码后缀
""", )
phone_num = "123 - 4567 - 8901"
match_verbose = (phone_num)
if match_verbose:
print(f"使用VERBOSE模式匹配手机号:{()}")

五、实战应用技巧

1. 邮箱地址验证


import re
def is_valid_email(email):
# 一个相对健壮的邮箱验证正则
pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
return (pattern, email) is not None
print(f"'test@' 是有效邮箱吗? {is_valid_email('test@')}")
print(f"'+tag@' 是有效邮箱吗? {is_valid_email('+tag@')}")
print(f"'invalid-email' 是有效邮箱吗? {is_valid_email('invalid-email')}")

2. 手机号码提取 (中国大陆简化版)


import re
def extract_phone_numbers(text):
# 匹配以13-19开头的11位数字手机号
pattern = r"1[3-9]\d{9}"
return (pattern, text)
text = "我的电话是 13812345678,你也可以打 13900001111 或者 17722223333。"
phones = extract_phone_numbers(text)
print(f"提取到的手机号:{phones}")

3. HTML 标签去除


注意: 使用正则表达式解析 HTML 是一种常见但不推荐的做法,因为它无法处理复杂的嵌套结构。对于可靠的 HTML 解析,请使用 BeautifulSoup 或 lxml 等库。import re
html_content = "<p>这是一个<b>带标签</b>的文本。</p><a href='#'>链接</a>"
# 匹配任意 <tag> 或 </tag>
clean_text = (r"<[^>]+>", "", html_content)
print(f"去除HTML标签后的文本:{clean_text}")

六、总结与建议

正则表达式是 Python 程序员的利器,掌握其核心语法和 re 模块的常用函数,能极大提升文本处理的效率和灵活性。以下是一些建议:
多练习: 正则表达式的语法相对复杂,最好的学习方法是多写、多测试。
善用原始字符串: 始终使用 r"..." 来定义正则表达式,避免不必要的转义困扰。
理解贪婪与非贪婪: 根据实际需求选择合适的量词匹配行为。
利用在线工具: 使用 、 等在线工具进行测试和调试,它们提供实时的匹配结果和详细解释。
优先编译: 对于会频繁使用的模式,使用 () 预编译以提升性能。
注意可读性: 对于复杂的正则表达式,考虑使用 标志来添加注释和格式化,使其更易读。
警惕过度使用: 并非所有字符串操作都需要正则表达式。对于简单的替换或查找,Python 的字符串方法(如 (), ())可能更简单高效。

通过本文的详细介绍和丰富的代码示例,相信您已经对 Python 的正则表达式有了更深入的理解,并能自如地运用这些常用代码解决实际问题。

2025-11-02


上一篇:Python文件处理深度指南:从基础到高级的文件系统交互实战

下一篇:Python 进阶:深入理解嵌套函数与闭包的强大魅力