电话号码有「138-1234-5678」,有「138 1234 5678」,还有一次写成了一首小诗。模式能把它们都找出来,按空格切分做不到。正则到底捕获了什么?
正则表达式描述一种文本模式。Python 的 re 模块可以查找第一个匹配、收集全部匹配,或替换匹配到的文本。模式先用原始字符串,例如 r"\d+",这样反斜杠会原样交给正则引擎。 你还会区分 match、search 和 fullmatch,解析命名分组,并转义字面量。
查找第一个匹配
re.search 返回模式第一次出现处的匹配对象;没有匹配时返回 None。用 group() 读取匹配到的文本。真实程序里一定要处理 None 的情况。
import re
match = re.search(r"\d+", "Order 42 is ready")
print(match.group())第一个数字
找出所有匹配
re.findall 返回所有不重叠匹配组成的列表。字符类 [A-Z] 匹配一个大写字母;量词 + 表示一次或多次。结果可以直接计数或拼接。
import re
print(re.findall(r"\d+", "a1 b22 c333"))
print(re.findall(r"[A-Z]", "PyTHON"))所有匹配片段
替换匹配文本
re.sub 会把每个匹配替换成指定字符串。这里的 \s+ 表示一个或多个空白字符,所以连续空格会合并成一个。模式用原始字符串,并先在小样本上测试替换结果。
import re
text = "latte tea mocha"
print(re.sub(r"\s+", " ", text))规范化空白
match、search 与 fullmatch
match() 从开头锚定;search() 扫描任意位置;fullmatch() 要求整个字符串都匹配。选择能表达规则的最弱工具。None 是正常结果,调用 group() 前先检查。
import re
text = "order 42 ready"
print(bool(re.match(r"\d+", text)))
print(bool(re.search(r"\d+", text)))
print(bool(re.fullmatch(r"\d+", "42")))
print(re.search(r"\d+", text).group())前缀、任意位置,还是整个字段
命名分组解析记录
命名分组让模式更可读,也能直接返回字典。把严格形状写进模式,再按名字读取字段。调用 groupdict() 前检查 None;匹配失败不等于空记录。
import re
pattern = re.compile(r"(?P<date>\d{4}-\d{2}-\d{2})\|(?P<drink>[a-z]+)\|(?P<price>\d+\.\d{2})")
match = pattern.fullmatch("2026-10-28|latte|4.50")
print(match.groupdict() if match else "invalid")
bad = pattern.fullmatch("2026-10-28|latte|free")
print(bad is None)严格形状,可读字段
转义字面量,提防贪婪
re.escape() 让用户文本可以安全嵌入模式。贪婪量词会尽量多取;在量词后加问号会变成惰性。模式越窄越好,并且同时用有效和无效样本测试。
import re
needle = "a.b"
text = "a.b axb"
print(re.findall(re.escape(needle), text))
html = "<b>bold</b>"
print(re.findall(r"<.+?>", html))
print(re.findall(r"<.+>", html))转义文本;惰性不一定更好
本课要点
- 正则模式优先使用原始字符串。
- re.search 返回第一个匹配或 None。
- re.findall 返回所有匹配组成的列表。
- re.sub 会替换文本中所有匹配部分。
- 整字段校验用 fullmatch,找出现用 search,字面量文本先 re.escape。
常见错误
- 忘记 r 前缀,让 Python 先解释反斜杠。
- search 返回 None 时仍然调用 group()。
- 模式范围过宽,匹配到了并不想要的文本。
- 直接拼接未转义的用户输入来构造模式。
从 "a1 b22 c333" 中提取所有数字,再用逗号连接结果。
动手试试
改改变量、先预测输出,再运行代码。第一次点击“运行代码”时会下载 Python 运行时,可能需要几秒钟。
练习本课
12 道题,作答后立即看到解释。