正则表达式笔记

Python 正则表达式学习笔记

一、正则表达式是什么

正则表达式(Regular Expression)是一种描述字符串规则的工具,常用于查找、提取、验证和替换文本。例如,从文章中提取数字、检查输入格式、删除多余空格,都可以使用正则表达式。

Python 通过内置的 re 模块使用正则:

1
2
3
4
5
6
7
import re

text = "小明今年20岁"
result = re.search(r"\d+", text)

if result:
print(result.group()) # 20

这里的 \d 表示数字,+ 表示前面的内容出现一次或多次,因此 \d+ 可以匹配连续数字。

正则适合处理具有明确模式的文本。简单的固定内容判断,使用 in、startswith() 或 replace() 往往更直观。

二、为什么通常使用原始字符串

Python 字符串和正则表达式都使用反斜杠,容易产生转义冲突。建议在正则字符串前加 r:

1
pattern = r"\d+"

r 表示原始字符串,让 Python 保留反斜杠,交给正则引擎解释。

例如,正则中的 \b 表示单词边界,但普通 Python 字符串中的 "\b" 表示退格字符。因此,写正则时通常使用 r"\b"。

需要注意:原始字符串并不是让正则失去转义功能,而是减少 Python 字符串这一层的转义处理。

三、常用字符与字符集合

写法 含义 示例
. 默认匹配除换行符外的任意字符 a.c
\d 匹配一个数字字符 \d\d
\D 匹配一个非数字字符 \D+
\w 匹配 Unicode 字母、数字及下划线 \w+
\W 匹配不属于 \w 的字符 \W+
\s 匹配空白字符,如空格、制表符、换行符 \s+
\S 匹配非空白字符 \S+
[abc] 匹配 a、b、c 中的任意一个 [abc]+
[0-9] 匹配一个 ASCII 数字 [0-9]+
[^abc] 匹配除 a、b、c 外的任意一个字符 [^abc]+

方括号表示字符集合,每次匹配一个字符。例如 [abc] 可以匹配 "a",但它不表示匹配完整的 "abc"。

在 Python 默认的 Unicode 模式下,\d 不只匹配 0~9,\w 也可以匹配汉字。若要求输入只能包含英文和数字,应明确写出范围:

1
pattern = r"[A-Za-z0-9]+"

四、数量限定符

数量限定符用于规定前面的字符或分组出现多少次。

写法 含义
* 零次或多次
+ 一次或多次
? 零次或一次
{n} 恰好 n 次
{n,} 至少 n 次
{n,m} 至少 n 次,最多 m 次

例如:

1
2
3
r"[0-9]{6}"       # 连续6位数字
r"[A-Za-z]{2,5}" # 连续2~5个英文字母
r"-?[0-9]+" # 可带负号的整数

-? 表示负号可以出现一次,也可以不出现。因此,最后一个模式能够匹配 "25" 和 "-25"。

限定符只作用于它前面的一个单位:ab+ 表示一个 a 后跟一个或多个 b;(ab)+ 才表示 "ab" 整体重复。

五、位置与边界

^ 表示字符串开头,$ 表示字符串结尾附近的位置。需要注意,$ 也可以匹配字符串末尾换行符之前的位置。

1
2
r"^Hello"  # 从开头匹配 Hello
r"world$" # 匹配结尾处的 world

\b 表示单词边界,即 \w 与 \W 之间,或相关的字符串起止位置:

1
2
re.findall(r"\bcat\b", "cat cats")
# ['cat']

它适合区分英文单词,但不能直接当作中文分词工具。

如果目标是验证整个字符串,优先使用 re.fullmatch(),这样表达的意图更清楚。

六、Python 中常用的匹配函数

1. re.search():寻找第一个匹配

它会扫描字符串,找到第一个符合规则的内容。成功返回匹配对象,失败返回 None。

1
2
3
4
m = re.search(r"[0-9]+", "价格:128元")

if m:
print(m.group()) # 128

2. re.match():只从开头匹配

1
2
re.match(r"[0-9]+", "123abc")  # 成功
re.match(r"[0-9]+", "abc123") # None

从开头匹配成功,并不意味着整个字符串都符合规则。

3. re.fullmatch():匹配整个字符串

1
2
re.fullmatch(r"[0-9]+", "123")     # 成功
re.fullmatch(r"[0-9]+", "123abc") # None

适合检查用户输入的格式。

4. re.findall():查找所有匹配

1
2
re.findall(r"[0-9]+", "语文90,数学95")
# ['90', '95']

结果通常是字符串列表;如果模式包含捕获分组,返回结构会发生变化。

5. re.finditer():逐个获取匹配对象

1
2
for m in re.finditer(r"[0-9]+", "a12b345"):
print(m.group(), m.span())

group() 获取匹配内容,span() 获取起止位置,结束位置不包含在匹配范围内。

七、分组与“或者”

圆括号可以把多个字符作为一个整体,也可以捕获其中的内容:

1
2
3
4
5
6
7
8
9
m = re.fullmatch(
r"([0-9]{4})-([0-9]{2})-([0-9]{2})",
"2026-10-12"
)

if m:
print(m.group(0)) # 2026-10-12
print(m.group(1)) # 2026
print(m.groups()) # ('2026', '10', '12')

group(0) 是完整匹配,后面的编号对应各个捕获分组。

如果只需要分组,不需要提取内容,可以使用非捕获分组 (?:...)。

竖线 | 表示“或者”:

1
r"(?:cat|dog)"

它可以匹配 "cat" 或 "dog"。给选择范围加上括号,能让模式的结构更明确。

八、贪婪与非贪婪匹配

数量限定符默认采用贪婪匹配:在允许后续模式成功的前提下,尽可能匹配更多内容。

1
2
3
4
text = "<b>你好</b><b>世界</b>"

re.findall(r"<b>.*</b>", text)
# ['<b>你好</b><b>世界</b>']

在限定符后加 ?,可以改为非贪婪匹配:

1
2
re.findall(r"<b>.*?</b>", text)
# ['<b>你好</b>', '<b>世界</b>']

非贪婪表示优先尝试较短的匹配,仍然需要满足整个模式。复杂 HTML 应使用专门的解析器处理。

九、替换、拆分与常用标志

re.sub() 用于替换符合规则的内容:

1
2
re.sub(r"\s+", " ", "Python   正则\t学习")
# 'Python 正则 学习'

re.split() 可以按照多个分隔符拆分:

1
2
re.split(r"[,;,;]+", "苹果,香蕉;橘子")
# ['苹果', '香蕉', '橘子']

常用匹配标志包括:

  • re.I:忽略大小写。
  • re.M:让 ^、$ 也匹配每行的开头、结尾。
  • re.S:让 . 也能匹配换行符。
  • re.A:让 \d、\w 等采用 ASCII 范围。

同一个模式需要反复使用时,可以先编译:

1
2
pattern = re.compile(r"[0-9]+")
print(pattern.findall("a12b34"))

十、实战思路与常见错误

例如,检查用户名:长度为 4~16 位,以英文字母开头,其余字符只能是英文字母、数字或下划线。

1
2
3
4
5
def is_valid_username(name):
return re.fullmatch(
r"[A-Za-z][A-Za-z0-9_]{3,15}",
name
) is not None

首字符已经占了一位,因此后面的长度是 {3,15}。

学习和编写正则时,可以按以下顺序进行:

  1. 明确要查找、提取、替换还是验证。
  2. 拆分规则,确定字符范围、数量和位置。
  3. 先写简单模式,再加入分组或边界。
  4. 测试正常、错误、空字符串和长度边界情况。

常见错误包括:未判断 None 就调用 group();把 match() 当作完整验证;忘记转义字面量的点号;使用捕获分组后误解 findall() 的返回结果。

要匹配真正的点号,应写 r"\."。要把外部字符串当作普通文本嵌入模式,可以使用 re.escape()。

最后,正则通常负责检查“形式”,业务代码负责检查“含义”。例如,日期模式能确认 "2026-99-99" 的结构,却不能确认日期真实存在;还需要日期解析或额外校验。


正则表达式笔记
https://umeshu.cn/2021/03/12/RegularExp/正则表达式笔记/
作者
IchiharaYuuko
发布于
2021年3月12日
许可协议