Python 的正则表达式主要通过 re 模块实现,用于字符串的匹配、查找、替换、分割等操作。以下是系统的用法讲解,包含核心概念、常用函数、语法规则和实战示例。

一、核心前置知识

  1. 正则表达式:一种描述字符串模式的语法,用于匹配 / 提取符合规则的字符串片段。

  2. re 模块:Python 内置的正则处理模块,需先导入:import re

  3. 原始字符串:正则中建议使用 r'pattern'(原始字符串),避免反斜杠 \ 被 Python 转义(如 r'\d''\\d' 更简洁)。

二、常用正则语法规则(核心)

语法

说明

示例

.

匹配任意单个字符(除换行符)

r'a.b' 匹配 a1ba*b

\d

匹配数字(0-9)

r'\d+' 匹配 123

\D

匹配非数字

r'\D+' 匹配 abc

\w

匹配字母 / 数字 / 下划线([a-zA-Z0-9_])

r'\w' 匹配 5x

\W

匹配非字母 / 数字 / 下划线

r'\W' 匹配 !@

\s

匹配空白符(空格、制表符、换行)

r'\s' 匹配 、\t

\S

匹配非空白符

r'\S' 匹配 a1

^

匹配字符串开头

r'^abc' 匹配 abc123 开头的 abc

$

匹配字符串结尾

r'abc$' 匹配 123abc 结尾的 abc

*

匹配前一个字符 0 次或多次

r'a*' 匹配 ''aaaa

+

匹配前一个字符 1 次或多次

r'a+' 匹配 aaaa(不匹配空)

?

匹配前一个字符 0 次或 1 次

r'a?' 匹配 ''a

{n}

匹配前一个字符恰好 n 次

r'a{3}' 匹配 aaa

{n,}

匹配前一个字符至少 n 次

r'a{2,}' 匹配 aaaaa

{n,m}

匹配前一个字符 n 到 m 次

r'a{2,4}' 匹配 aaaaaaaaa

[]

字符集,匹配其中任意一个字符

r'[abc]' 匹配 abc

[^]

否定字符集,匹配不在其中的字符

r'[^abc]' 匹配 1x

`

`

或,匹配左侧或右侧的模式

`r'abc

123'匹配abc123`

()

分组,提取匹配的子串

r'(\d{3})-(\d{8})' 匹配手机号段

\

转义特殊字符(如 .*

r'\.' 匹配 .(而非任意字符)

三、re 模块核心函数

1. re.match ():从字符串开头匹配

  • 语法:re.match(pattern, string, flags=0)

  • 返回:匹配对象(成功)/ None(失败)

  • 常用方法:group()(获取匹配内容)、span()(获取匹配位置)

import re

# 匹配开头的 "python"
result = re.match(r'python', 'python123')
if result:
    print(result.group())  # 输出:python
    print(result.span())   # 输出:(0, 6)

# 开头不匹配,返回 None
result2 = re.match(r'python', '123python')
print(result2)  # 输出:None

2. re.search ():在字符串任意位置匹配(仅第一个)

  • 语法:re.search(pattern, string, flags=0)

  • 返回:匹配对象(成功)/ None(失败)

# 任意位置匹配 "python"
result = re.search(r'python', '123python456')
if result:
    print(result.group())  # 输出:python
    print(result.span())   # 输出:(3, 9)

3. re.findall ():匹配所有符合规则的子串,返回列表

  • 语法:re.findall(pattern, string, flags=0)

  • 返回:列表(无匹配则返回空列表)

# 提取所有数字
result = re.findall(r'\d+', 'abc123def456')
print(result)  # 输出:['123', '456']

# 提取所有邮箱(简单匹配)
result2 = re.findall(r'\w+@\w+\.\w+', '邮箱1:test@163.com,邮箱2:abc@qq.com')
print(result2)  # 输出:['test@163.com', 'abc@qq.com']

4. re.sub ():替换匹配的子串

  • 语法:re.sub(pattern, repl, string, count=0, flags=0)

  • 参数:repl 为替换后的字符串,count 为替换次数(0 表示全部)

  • 返回:替换后的新字符串

# 替换所有数字为 "*"
result = re.sub(r'\d+', '*', '手机号:13812345678')
print(result)  # 输出:手机号:*

# 只替换前2个数字段
result2 = re.sub(r'\d+', '*', '123abc456def789', count=2)
print(result2)  # 输出:*abc*def789

5. re.split ():按匹配的子串分割字符串

  • 语法:re.split(pattern, string, maxsplit=0, flags=0)

  • 参数:maxsplit 为最大分割次数(0 表示全部)

  • 返回:分割后的列表

# 按逗号/空格分割字符串
result = re.split(r'[, ]+', 'a,b c  d')
print(result)  # 输出:['a', 'b', 'c', 'd']

# 最多分割2次
result2 = re.split(r'[, ]+', 'a,b c  d', maxsplit=2)
print(result2)  # 输出:['a', 'b', 'c  d']

6. re.compile ():预编译正则表达式(提升多次匹配效率)

  • 语法:pattern = re.compile(pattern, flags=0)

  • 用途:多次使用同一正则时,预编译可减少重复解析开销

# 预编译正则
phone_pattern = re.compile(r'1[3-9]\d{9}')  # 匹配手机号

# 多次使用编译后的正则
result1 = phone_pattern.search('手机号:13812345678')
result2 = phone_pattern.findall('13987654321 12345678901')
print(result1.group())  # 输出:13812345678
print(result2)          # 输出:['13987654321']

四、常用 flags(匹配模式)

标志

说明

re.I

忽略大小写

re.M

多行模式(^/$ 匹配每行开头 / 结尾)

re.S

单行模式(. 匹配包括换行符在内的所有字符)

re.X

忽略正则中的空格和注释(便于编写复杂正则)

示例:

# 忽略大小写匹配
result = re.match(r'python', 'Python123', re.I)
print(result.group())  # 输出:Python

# 多行模式匹配每行开头的数字
text = '1. abc\n2. def\n3. ghi'
result = re.findall(r'^\d', text, re.M)
print(result)  # 输出:['1', '2', '3']

五、分组与反向引用

1. 分组提取(()`)

通过 group(n) 获取第 n 个分组(group(0) 是整个匹配内容,group(1) 是第一个分组,依此类推)。

# 提取姓名和年龄
text = '姓名:张三,年龄:25'
result = re.search(r'姓名:(.*),年龄:(\d+)', text)
if result:
    print(result.group(0))  # 输出:姓名:张三,年龄:25
    print(result.group(1))  # 输出:张三
    print(result.group(2))  # 输出:25

2. 反向引用(\n\g<n>

在正则中引用已匹配的分组,或在替换时使用 \n 引用。

# 匹配重复的字符(如 "aa"、"bb")
result = re.findall(r'(\w)\1', 'aabbcc1122')
print(result)  # 输出:['a', 'b', 'c', '1', '2']

# 替换:将 "姓名:张三" 转为 "张三(姓名)"
text = '姓名:张三'
result = re.sub(r'姓名:(.*)', r'\1(姓名)', text)
print(result)  # 输出:张三(姓名)

六、实战示例

示例 1:验证手机号

def is_phone(phone):
    pattern = re.compile(r'^1[3-9]\d{9}$')
    return bool(pattern.match(phone))

print(is_phone('13812345678'))  # True
print(is_phone('12345678901'))  # False

示例 2:提取 HTML 中的链接

html = '<a href="https://www.baidu.com">百度</a><a href="https://www.python.org">Python</a>'
links = re.findall(r'href="(.*?)"', html)
print(links)  # 输出:['https://www.baidu.com', 'https://www.python.org']

示例 3:清理字符串中的特殊字符

text = '  这是一段包含@#¥%的文本 123...  '
# 去除特殊字符、数字和首尾空格
clean_text = re.sub(r'[^\u4e00-\u9fa5\s]', '', text).strip()
print(clean_text)  # 输出:这是一段包含的文本

七、注意事项

  1. 贪婪匹配 vs 非贪婪匹配

    • 默认是贪婪匹配(尽可能多匹配):r'\d+' 匹配 123 会全部匹配;

    • 非贪婪匹配(加 ?):r'\d+?' 匹配 123 只会匹配 1

  2. 转义字符:正则中的特殊字符(如 .*()需用 \ 转义,或放在 [] 中(如 [.] 匹配 .)。

  3. 性能:复杂正则或大量文本匹配时,优先用 re.compile() 预编译,避免重复解析。

  4. 边界处理:匹配手机号、邮箱等时,务必用 ^$ 限定开头结尾,避免部分匹配(如 138123456789 被误判为手机号)。