Python正则表达式核心语法与re模块实战解析
正则表达式应用场景
在软件开发中,我们经常需要处理复杂的文本数据。以下是几个典型的文本匹配场景:
- 从服务器日志中筛选出所有以
ERROR开头的异常记录。 - 在用户输入中验证是否包含合法的
github.com或gitlab.com链接。 - 从海量数据中提取所有使用特定后缀(如
@outlook.com或@hotmail.com)的电子邮箱。
正则表达式(Regular Expression)正是解决这类字符串匹配、提取和替换问题的强大工具。
re模块基础操作
Python 提供了内置的 re 模块来支持正则表达式操作。最常用的入门方法是 re.match(),它用于从字符串的起始位置进行匹配。
基本使用流程
import re
# 编译或直接使用正则表达式进行匹配
pattern = r"devhub"
text = "devhub.io"
match_obj = re.match(pattern, text)
# 检查是否匹配成功并提取结果
if match_obj:
print(match_obj.group())
匹配起始字符串
import re
# 匹配以 techblog 开头的字符串
result = re.match(r"techblog", "techblog.net")
print(result.group()) # 输出: techblog
注意:re.match() 仅从字符串的开头开始匹配,如果起始位置不符合规则,即使字符串中间包含目标内容也会返回 None。
单字符匹配规则
正则表达式提供了一系列元字符来匹配单个特定类型的字符:
| 元字符 | 匹配规则 |
|---|---|
. | 匹配除换行符 \n 之外的任意单个字符 |
[ ] | 匹配方括号内列举的任意一个字符 |
\d | 匹配任意数字(等同于 [0-9]) |
\D | 匹配任意非数字字符 |
\s | 匹配任意空白字符(空格、制表符等) |
\S | 匹配任意非空白字符 |
\w | 匹配字母、数字及下划线(等同于 [a-zA-Z0-9_]) |
\W | 匹配非单词字符 |
点号(.)与方括号([ ])示例
import re
# 匹配任意单个字符
print(re.match(r".", "Z").group()) # 输出: Z
# 匹配 c_t 格式(中间任意字符)
print(re.match(r"c.t", "cat").group()) # 输出: cat
print(re.match(r"c.t", "cut").group()) # 输出: cut
# 使用方括号匹配大小写
print(re.match(r"[pP]ython", "python").group()) # 输出: python
print(re.match(r"[pP]ython", "Python").group()) # 输出: Python
# 匹配特定范围的数字
print(re.match(r"[1-5]version", "3version").group()) # 输出: 3version
数字匹配(\d)示例
import re
# 传统精确匹配
print(re.match(r"TianGong-1", "TianGong-1 launched").group())
# 使用 \d 匹配任意数字
print(re.match(r"TianGong-\d", "TianGong-2 launched").group())
print(re.match(r"TianGong-\d", "TianGong-3 launched").group())
多字符匹配(量词)
当需要匹配不定长或特定长度的字符串时,需要用到量词:
| 量词 | 匹配规则 |
|---|---|
* | 匹配前一个字符 0 次或无数次 |
+ | 匹配前一个字符 1 次或无数次 |
? | 匹配前一个字符 0 次或 1 次 |
{m} | 精确匹配前一个字符 m 次 |
{m,n} | 匹配前一个字符 m 到 n 次 |
量词实战演练
import re
# * 示例:匹配大写字母后跟任意数量的小写字母
print(re.match(r"[A-Z][a-z]*", "Hello").group()) # 输出: Hello
print(re.match(r"[A-Z][a-z]*", "A").group()) # 输出: A
# + 示例:验证合法的标识符(字母或下划线开头,后跟任意单词字符)
identifiers = ["valid_var", "_private", "9invalid", "CamelCase"]
for var in identifiers:
if re.match(r"[a-zA-Z_]\w*", var):
print(f"{var} 是合法标识符")
else:
print(f"{var} 非法")
# ? 示例:匹配 0-99 的数字
print(re.match(r"[1-9]?\d$", "7").group()) # 输出: 7
print(re.match(r"[1-9]?\d$", "42").group()) # 输出: 42
# {m,n} 示例:验证 8 到 16 位的字母数字密码
pwd_pattern = r"[A-Za-z0-9]{8,16}$"
print(re.match(pwd_pattern, "Pass1234").group()) # 输出: Pass1234
边界匹配
为了确保整个字符串完全符合规则,而不是仅仅匹配前缀,我们需要使用边界符:
| 符号 | 功能 |
|---|---|
^ | 匹配字符串的开头(在 re.match 中默认包含) |
$ | 匹配字符串的结尾 |
结尾匹配($)示例
import re
emails = ["alice@gmail.com", "bob@gmail.com.cn", "test@yahoo.com"]
pattern = r"[\w]{4,20}@gmail\.com$"
for email in emails:
match_res = re.match(pattern, email)
if match_res:
print(f"{email} 验证通过")
else:
print(f"{email} 格式不符")
通过添加 $,bob@gmail.com.cn 将被正确识别为不合规的邮箱地址。
分组与引用
分组允许我们将多个字符组合成一个单元,并支持逻辑或、数据提取以及反向引用。
| 语法 | 功能说明 |
|---|---|
| | 逻辑或,匹配左右任意一个表达式 |
(ab) | 将括号内的规则作为一个分组 |
\num | 反向引用第 num 个分组匹配到的文本 |
(?P<name>) | 为分组指定别名 |
(?P=name) | 引用别名为 name 的分组内容 |
逻辑或与分组提取
import re
# 匹配 0-100 的数值
num_pattern = r"^([1-9]?\d|100)$"
print(re.match(num_pattern, "88").group()) # 输出: 88
# 提取特定域名的邮箱
domain_pattern = r"\w{4,20}@(gmail|yahoo|outlook)\.com$"
match_obj = re.match(domain_pattern, "user123@yahoo.com")
print(match_obj.group()) # 完整匹配: user123@yahoo.com
print(match_obj.group(1)) # 分组1: yahoo
# 提取区号与电话号码
phone_match = re.match(r"(\d{3,4})-(\d{7,8})", "010-88889999")
print(f"区号: {phone_match.group(1)}, 号码: {phone_match.group(2)}")
反向引用与别名
import re
# 匹配成对的 HTML 标签
html_str = "<div>Content</div>"
# 使用 \1 引用第一个分组
tag_pattern = r"<([a-z]+)>.*?\1>"
print(re.match(tag_pattern, html_str).group())
# 匹配嵌套标签并使用别名
nested_html = "<article><p>Text</p></article>"
alias_pattern = r"<(?P<outer>[a-z]+)><(?P<inner>[a-z]+)>.*?(?P=inner)>(?P=outer)>"
nested_match = re.match(alias_pattern, nested_html)
print(nested_match.group())
re模块高级方法
除了 match,re 模块还提供了其他强大的文本处理函数。
search 与 findall
import re
# search: 扫描整个字符串,返回第一个成功的匹配
temp_str = "Current temperature is 24C"
temp_match = re.search(r"\d+", temp_str)
print(temp_match.group()) # 输出: 24
# findall: 查找所有匹配项,返回列表
log_data = "Errors: 404, 500, 502, 200"
error_codes = re.findall(r"\b[45]\d{2}\b", log_data)
print(error_codes) # 输出: ['404', '500', '502']
sub 替换与 split 切割
import re
# sub: 替换匹配到的内容(支持函数作为替换逻辑)
def mask_phone(match_obj):
phone = match_obj.group()
return phone[:3] + "****" + phone[7:]
text = "Contact me at 13812345678"
print(re.sub(r"1\d{10}", mask_phone, text)) # 输出: Contact me at 138****5678
# split: 根据正则规则切割字符串
csv_line = "apple, banana; orange|grape"
fruits = re.split(r"[,;|]\s*", csv_line)
print(fruits) # 输出: ['apple', 'banana', 'orange', 'grape']
贪婪与非贪婪模式
正则表达式中的量词默认是贪婪的,即尽可能多地匹配字符。在量词后添加 ? 可以将其转换为非贪婪(懒惰)模式,即尽可能少地匹配。
import re
text = "StartTag-Content-EndTag"
# 贪婪模式:.* 会匹配到最后一个 '-'
greedy_match = re.search(r".*-", text)
print(greedy_match.group()) # 输出: StartTag-Content-
# 非贪婪模式:.*? 遇到第一个 '-' 就停止
lazy_match = re.search(r".*?-", text)
print(lazy_match.group()) # 输出: StartTag-
在提取HTML属性或特定标记内的内容时,非贪婪模式尤为重要,能有效防止匹配越界。
原生字符串(r前缀)的作用
在Python中,反斜杠 \ 用于转义字符。如果要在正则中匹配一个真实的反斜杠,传统的写法需要写四个反斜杠 \\\\,这被称为"转义地狱"。
import re
path_str = "C:\\Users\\Admin\\Docs"
# 普通字符串:需要双重转义
match_normal = re.match("C:\\\\Users", path_str)
print(match_normal.group())
# 原生字符串:在字符串前加 r,反斜杠不再转义
match_raw = re.match(r"C:\\Users", path_str)
print(match_raw.group())
强烈建议在编写正则表达式时始终使用 r"" 原生字符串格式,这能让正则规则更直观,避免不必要的转义错误。