今天你 AI 赋能了吗?打开浏览器,在地址栏输入 xuefulu.ai ,按下 Enter 即可访问学府路1号!
2026年10月14日 星期三 10:40:30
常用网址: | | 学府路1号 https://xuefulu.ai | 下载 Python https://xuefulu.ai/Python.html | 下载 TRAE https://xuefulu.ai/Trae.html

一、正则是什么:用「模式」找文字

第 10 篇《字符串》里学过 'abc' in text、text.find('abc') 这类找法。它们只能找一模一样的内容。可真实需求经常是「找一段长得像的东西」:

需求不用正则怎么写用正则
找出一段文字里所有的数字循环 + 判断每个字符是不是 0-9\d+
判断这串是不是手机号写一长串 if 判断长度、首位、每一位1[3-9]\d{9}
把 2026/10/04 改成 2026-10-04切分、拼接、判断格式一条替换搞定
找出所有邮箱几乎写不出来[\w.]+@[\w.]+\.[a-z]{2,}

正则表达式(regular expression)就是用来描述「一段文字长什么样」的一套小语言。它不解决新问题,但能把「逐字符判断」这种啰嗦活压缩成一行。

Python
import re

text = '点了一杯杨枝甘露,花了 18 元,送到宿舍 3 号楼'
print(re.findall(r'\d+', text))

输出:

输出
['18', '3']
正则的正确定位是:「临时用来捞数据」的工具。一段文本里反复要取同一种东西,用它最省事;但如果你的程序里有十几条长正则,那往往该换成 csv / json 这种有结构的数据格式(第 28 篇《常用标准库》里讲过)。

课程思政|科技自立自强 · 看懂模式,才不被模式牵着走

正则是一套描述「文字长什么样」的小语言,手机号校验、日志分析、数据清洗都靠它,如今几乎每个工具都把它当标配。网上一搜就有现成模式,可抄来的东西一旦匹配不上,你往往不知道从哪儿下手改。花点时间把 \d、+、() 这几个基本符号弄懂,这门手艺才算真正长在自己身上。

二、re 的四个主力函数

re 是标准库,跟着 Python 一起装好。日常干活,四个函数覆盖九成场景:

函数干什么返回什么
search(模式, 文本)在文本里找第一个符合的匹配对象,找不到是 None
match(模式, 文本)只从开头匹配匹配对象,找不到是 None
findall(模式, 文本)找出全部符合的列表(最常用)
sub(模式, 新内容, 文本)把符合的替换掉替换后的新字符串

2.1 search:找第一个

Python
import re

text = 'abc123def'
m = re.search(r'\d+', text)
print(m)
print('取到内容:', m.group())
print('它在哪里:', m.span())

输出:

输出
<re.Match object; span=(3, 6), match='123'>
取到内容: 123
它在哪里: (3, 6)

2.2 match:只认开头——这是最容易搞错的一个

Python
import re

print('search 找 abc123 里的数字:', re.search(r'\d+', 'abc123').group())
print('match  找 abc123 里的数字:', re.match(r'\d+', 'abc123'))
print('match  找 123abc 里的数字:', re.match(r'\d+', '123abc').group())

输出:

Python
search 找 abc123 里的数字: 123
match  找 abc123 里的数字: None
match  找 123abc 里的数字: 123

search 从任意位置开始找,match 只认开头。上面第二个打印 None,就是因为字符串开头是字母而不是数字。

要用哪一个是看需求,不是看哪个顺手。判断「这串是不是手机号」要用 fullmatch(整串完全匹配);判断「里面有没有手机号」才用 search。用错会导致「只要开头像就算通过」这种漏洞。

2.3 findall:一次找全部

Python
import re

text = 'a1b22c333'
print(re.findall(r'\d+', text))
print(re.findall(r'[a-z]+', text))

输出:

输出
['1', '22', '333']
['a', 'b', 'c']

返回的是字符串列表——想当成数字算,还要自己转 int()(和 csv 那一篇的坑一样)。

2.4 sub:替换

Python
import re

print(re.sub(r'\d+', '#', 'a1b22c333'))
print(re.sub(r'\d+', '', 'a1b22c333'))      # 换成空串 = 删掉
print(re.sub(r'\s+', ' ', '杨枝甘露   18   少冰'))  # 多个空白压成一个

输出:

输出
a#b#c#
abc
杨枝甘露 18 少冰
re.sub(r'\s+', ' ', 文本) 是清洗数据时最常用的一条:把连续的空格、制表符统统压成一个空格。做数据整理时几乎每个项目都会用到。

三、元字符速查表

正则的「词汇」不多,下面这张表覆盖了绝大多数日常用法。不用背,用的时候回来查。

3.1 表示「某一类字符」

写法含义匹配的例子
.任意一个字符(默认不含换行)a、1、中
\d一个数字0~9
\D一个非数字a、空格
\w一个「字」:字母、数字、下划线,以及中文a、5、_、中
\s一个空白字符空格、Tab、换行
[abc]方括号里的任意一个a 或 b 或 c
[a-z]a 到 z 里的任意一个g
[^0-9]方括号里之外的任意一个(^ 是「除了」)a、空格

3.2 表示「位置」

写法含义说明
^字符串开头在方括号里表示「除了」,位置不同意思不同
$字符串结尾\d+$ 表示以数字结尾
\b单词边界找独立的词时用

3.3 表示「几个」——量词

写法含义例子
*0 个或多个a*b 能匹配 b、ab、aaab
+1 个或多个(至少一个)a+b 不能匹配 b
?0 个或 1 个a?b 能匹配 b、ab
{3}恰好 3 个\d{3} 匹配 123
{2,5}2 到 5 个\d{2,5}
{2,}至少 2 个\d{2,}
Python
import re

print('+ 至少要一个:', re.findall(r'a+b', 'b ab aab aaab'))
print('* 可以一个都没有:', re.findall(r'a*b', 'b ab aab aaab'))
print('{3} 恰好三个:', re.findall(r'\d{3}', '12 123 1234'))

输出:

输出
+ 至少要一个: ['ab', 'aab', 'aaab']
* 可以一个都没有: ['b', 'ab', 'aab', 'aaab']
{3} 恰好三个: ['123', '123']

看第三行要留意:1234 里被找出来的也是 123——{3} 是「恰好三个」,多出来的第四个不参与。想「至少三个」得写 \d{3,}。

四、贪婪与懒惰:.* 到底吃多少

量词有个默认脾气:能多吃就多吃,这叫「贪婪」(greedy)。行为差异在下面这个例子里看得最清楚:

Python
import re

text = '<a><b>'
print('贪婪   <.*> :', re.findall(r'<.*>', text))
print('懒惰 <.*?> :', re.findall(r'<.*?>', text))

输出:

输出
贪婪   <.*> : ['<a><b>']
懒惰 <.*?> : ['<a>', '<b>']

两行只差一个 ?,结果完全不同:

写法叫法怎么吃这里的结果
<.*>贪婪一路吃到最远的那个 >一整串 <a><b>
<.*?>懒惰(非贪婪)吃到最近的那个就停两段 <a> 和 <b>

想变懒惰,就是在量词后面再加一个 ?:*?、+?、??、{2,5}? 都成立。

Python
import re

text = '价格:12.5元,折扣价 8.8元'
print('贪婪:', re.findall(r'\d+\.?\d*', text))
print('再看一对引号:')
print('  贪婪  "(.*)" :', re.findall(r'"(.*)"', '他说"你好",又说"再见"'))
print('  懒惰 "(.*?)" :', re.findall(r'"(.*?)"', '他说"你好",又说"再见"'))

输出:

示意
贪婪: ['12.5', '8.8']
再看一对引号:
  贪婪  "(.*)" : ['你好",又说"再见']
  懒惰 "(.*?)" : ['你好', '再见']
判断口诀:如果你要的是「每一段分别取出来」,就加 ? 变懒惰;如果你要的是「从第一个到最后一个整段」,就用默认的贪婪。实际写的时候,见到 .* 先想一下这个问题,能省掉很多「结果为什么是一整坨」的困惑。

AI 助手小贴士|元字符是正则的字母表,速查表交给 AI 生成最省事。

打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:把 . \d \w \s + * ? {m,n} 整理成一张表:符号、含义、一个能匹配的例子和一个不匹配的例子

课程思政|工匠精神与严谨求实 · 差一个问号,结果差很远

贪婪的 .* 一路吃到最远的那个符号,懒惰的 .*? 吃到最近的就停,两行只差一个 ?,结果却完全不同。正则最考验的,正是这种「以实际匹配为准」的严谨:别凭感觉说「应该能匹配上」,写一个样例跑一遍再下结论。这份较真用到统计和分析里,就是数据人最该有的基本功。

五、分组:把要的那一段单独拎出来

用圆括号 () 把模式中的一部分括起来,就把这部分单独标了出来,叫一个「组」(group)。

5.1 按编号取

Python
import re

m = re.search(r'(\d{4})-(\d{2})-(\d{2})', '日期:2026-10-04')
print('整段:', m.group(0))
print('第 1 组(年):', m.group(1))
print('第 2 组(月):', m.group(2))
print('第 3 组(日):', m.group(3))
print('一次全拿出来:', m.groups())

输出:

输出
整段: 2026-10-04
第 1 组(年): 2026
第 2 组(月): 10
第 3 组(日): 04
一次全拿出来: ('2026', '10', '04')

group(0) 是整段匹配,group(1) 往后才是各个括号里的内容。这个「0 号是全体」的规矩要记住。

5.2 按名字取(推荐)

Python
import re

m = re.search(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', '2026-10-04')
print('年:', m.group('year'))
print('一次全拿出来:', m.groupdict())

输出:

输出
年: 2026
一次全拿出来: {'year': '2026', 'month': '10', 'day': '04'}

写法是 (?P<名字>模式)。组一多,用名字取值比数编号靠谱得多——加一个括号就要重数一遍编号,很容易错。

5.3 在替换里用组

Python
import re

print(re.sub(r'(\d{4})/(\d{2})/(\d{2})', r'\1-\2-\3', '日期:2026/10/04'))

输出:

输出
日期:2026-10-04

替换内容里写 \1 \2,就等于「把第 1 组、第 2 组的内容放这里」。注意替换串也写了 r 前缀。

5.4 只想分组、不想占编号:(?:...)

Python
import re

print('带编号:  ', re.findall(r'(\d{3})-(\d{4})', '010-1234'))
print('不占编号:(?:...)', re.findall(r'(?:\d{3})-(\d{4})', '010-1234'))

输出:

输出
带编号:   [('010', '1234')]
不占编号:(?:...) ['1234']

加了 ?: 的括号只起到「把几个字符圈在一起」的作用,不参与编号、也不出现在结果里。当 findall 遇到多个组时,它返回的是「组的元组」而不是整段——这时候 ?: 就能把不关心的那部分摘掉。

六、实战:四个常用模式

下面四个模式是日常最常用的,读懂它们的构成,比背下来更有用。

6.1 手机号:1[3-9]\d{9}

Python
import re

text = '联系方式:13800138000,备用 13900139000,错误示范 12345678901'
print(re.findall(r'1[3-9]\d{9}', text))

输出:

输出
['13800138000', '13900139000']

拆开看:1 打头 → [3-9] 第二位是 3 到 9 → \d{9} 后面再跟 9 位数字,一共 11 位。12345678901 没被选中,因为第二位是 2。

6.2 邮箱:[\w.]+@[\w.]+\.[a-z]{2,}

Python
import re

text = '注册邮箱 tangyuan@example.com 或 a@b.cn'
print(re.findall(r'[\w.]+@[\w.]+\.[a-z]{2,}', text))

输出:

输出
['tangyuan@example.com', 'a@b.cn']

拆开看:[\w.]+ 用户名(字母数字下划线点)→ @ → [\w.]+ 域名 → \. 一个真正的点 → [a-z]{2,} 结尾至少两位字母。

6.3 日期归一:把 / 换成 -

Python
import re

print(re.sub(r'(\d{4})/(\d{2})/(\d{2})', r'\1-\2-\3', '开学日期:2026/09/01'))

输出:

输出
开学日期:2026-09-01

6.4 提取并计算:把价格加起来

Python
import re

text = '薯片 5.5元,奶茶 12.8元,辣条 3.2元'
prices = [float(x) for x in re.findall(r'\d+\.\d+', text)]
print('价格:', prices)
print('合计:', round(sum(prices), 1))

输出:

输出
价格: [5.5, 12.8, 3.2]
合计: 21.5

这一段把「提取 → 转类型 → 计算」三步串了起来,是最实用的组合:findall 拿到的一定是字符串,要算数就得自己转。

要解决的问题模式用法
取所有数字\d+findall
取所有小数\d+\.\d+findall + float
验证是不是手机号1[3-9]\d{9}fullmatch
找所有邮箱[\w.]+@[\w.]+\.[a-z]{2,}findall
多个空白压成一个\s+sub
去掉首尾空格^\s+|\s+$sub(其实 strip() 更简单)

课程思政|数据安全与法治素养 · 能匹配,不等于能收集

这一节教你怎么用模式,从一大段文本里捞出手机号和邮箱。正则确实是「捞」数据的利器,但技术做得到,和该不该做是两回事。同学的手机号、邮箱都是受法律保护的个人信息,练手时不妨把它们换成隐藏中间四位的脱敏数据,既练了手艺,也守住了分寸——真实业务里,越权采集、违规使用是真要担责任的。

七、re.compile 与常用 flags

7.1 编译成对象,反复用

Python
import re

phone = re.compile(r'1[3-9]\d{9}')      # 编译一次

print(phone.findall('联系 13800138000'))
print(phone.search('我的号是 13900139000').group())
print(bool(phone.fullmatch('13800138000')))

输出:

输出
['13800138000']
13900139000
True

同一个模式要用很多次时,compile() 一次、之后重复使用,写起来也更整齐。

但别为了「性能」而 compile。Python 内部本来就会缓存最近用过的模式,写一两处 re.findall(...) 完全没问题。compile() 的真正好处是可读性:模式起个名字(phone)比在代码里到处撒一串符号清楚得多。

7.2 三个常用 flags

flag含义什么时候用
re.I忽略大小写(ignore case)找 python 时不想漏掉 Python
re.M多行模式:^ 和 $ 匹配每一行的行首行尾逐行处理一段文本
re.S让 . 也匹配换行跨行抓内容
Python
import re

print('不加 I:', re.findall(r'python', 'Python PYTHON python'))
print('加了 I:', re.findall(r'python', 'Python PYTHON python', re.I))

text = 'abc\n123\nxyz'
print('不加 M:', re.findall(r'^\d+', text))
print('加了 M:', re.findall(r'^\d+', text, re.M))

输出:

输出
不加 I: ['python']
加了 I: ['Python', 'PYTHON', 'python']
不加 M: []
加了 M: ['123']

多个 flag 用竖线连起来:re.I | re.M。

re.M 那一组的对比很值得注意:不加时 ^ 只认整段文字的开头(开头是 abc,所以没匹配到数字);加了 re.M 之后 ^ 变成「每一行的开头」,才把中间那行 123 捞出来。

八、常见坑

坑 1:忘了写 r 前缀

正则里的 \d、\s、\b 在普通字符串里是「无效转义」,Python 会警告你。虽然多数情况还能跑,但有些模式会被悄悄改掉意思,非常难查。

Python
import re

print(re.findall('\d+', 'a1b22'))      # 少了 r 前缀

输出:

输出
['1', '22']

运行会成功,但解释器会先给一句警告:

demo.py:2: SyntaxWarning: invalid escape sequence '\d'

处理办法:所有正则串一律加 r 前缀,写成 r'\d+'。这样反斜杠就是反斜杠本身,不会被 Python 先解释一遍。

坑 2:拿 None 去调 .group()

Python
import re

m = re.search(r'\d+', '这段文字里没有数字')
print(m.group())      # m 是 None

运行后会报错(逐字照抄解释器):

报错
AttributeError: 'NoneType' object has no attribute 'group'

search / match 找不到时返回的是 None,None 当然没有 group()。

处理办法:先判断再取。

Python
import re

m = re.search(r'\d+', '这段文字里没有数字')
if m:
    print('找到了:', m.group())
else:
    print('没找到')

输出:

输出
没找到

坑 3:把 match 当成 search 用

Python
import re

text = '订单号 A123'
print('match 只认开头:', re.match(r'\d+', text))
print('search 任意位置:', re.search(r'\d+', text).group())

输出:

Python
match 只认开头: None
search 任意位置: 123

处理办法:「这串整体是不是符合格式」用 fullmatch;「里面有没有符合的」用 search;match 只在明确「必须从开头算起」时才用。

坑 4:模式里该转义的没转义

. 在正则里是「任意字符」,想表示真正的点必须写 \.:

Python
import re

print('没转义:', re.findall(r'\d.\d', '1a2 1.2'))
print('转了义:', re.findall(r'\d\.\d', '1a2 1.2'))

输出:

输出
没转义: ['1a2', '1.2']
转了义: ['1.2']

要转义的还有:\. \* \+ \? \( \) \[ \] \{ \} \| \^ \$。找不准时,一个稳妥的判断是:这个符号在正则里有特殊含义吗?有就加反斜杠。

坑 5:findall 带分组后返回的东西变了

Python
import re

print('没有组:', re.findall(r'\d{3}-\d{4}', '010-1234'))
print('有一个组:', re.findall(r'(\d{3})-\d{4}', '010-1234'))
print('两个组:', re.findall(r'(\d{3})-(\d{4})', '010-1234'))

输出:

输出
没有组: ['010-1234']
有一个组: ['010']
两个组: [('010', '1234')]

没有组时返回「整段」;有组之后返回的就变成「组」了——一个组返回字符串,多个组返回元组,整段反而拿不到了。不想要这个效果就用 (?:...)。

坑 6:以为 * 和 + 是一回事

Python
import re

print('a* 匹配 bb:', re.findall(r'a*', 'bb'))
print('a+ 匹配 bb:', re.findall(r'a+', 'bb'))

输出:

输出
a* 匹配 bb: ['', '', '']
a+ 匹配 bb: []

* 允许「一个都没有」,所以在没有 a 的地方也会匹配出空字符串(这里出现了 3 次空匹配)。+ 要求至少一个,没有就什么都匹配不到。要「至少一个」的时候就写 +,别用 *。

九、报错手册

报错原文都是从本机解释器逐字抄下来的,对照着找就行。

报错 / 警告原文(最后一行)什么情况怎么改
AttributeError: 'NoneType' object has no attribute 'group'search / match 没找到(返回 None),却直接调了 .group()先 if m: 判断,再取 group()
re.PatternError: missing ), unterminated subpattern at position 0括号没配对(少写或多写 ))从头数一遍括号;长模式分段写、分段测
re.PatternError: bad character range方括号里的范围写反了,如 [9-0]改成 [0-9]
SyntaxWarning: invalid escape sequence '\d'正则串忘了加 r 前缀写成 r'\d+'
TypeError: expected string or bytes-like object把数字或其他类型传给了 re 的函数先 str(值) 转成字符串
re.PatternError: nothing to repeat at position 0把量词写在了开头,如 *abc量词必须跟在「要重复的东西」后面

下面几条是「不报错,但结果不对」的,比报错更值得当心:

现象原因怎么改
只匹配到开头,中间明明有用了 match改用 search
本来想要好几段,结果全挤成一段用了贪婪的 .*加 ? 变懒惰:.*?
findall 返回的元组少了一截模式里加了分组改变了返回内容不想要的组写成 (?:...)
fullmatch 判手机号总是不通过字符串带了空格或换行先 .strip() 再判

AI 助手小贴士|正则靠实战题养手感。

打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:出 3 道正则题:提取文本里所有金额、校验 18 位身份证号(末位可能是 X)、把各种格式的日期统一成 YYYY-MM-DD。给我测试文本,我写完你判分

十、十个基础案例

下面十个例子都很短,建议每个都亲手敲一遍、跑一遍。

1. 把一句话里的数字全捞出来

Python
import re
print(re.findall(r'\d+', '奶茶 18 元,薯条 12 元,鸡翅 20 元'))

输出:

输出
['18', '12', '20']

2. 判断一串是不是 11 位手机号

Python
import re
def is_phone(s):
    return bool(re.fullmatch(r'1[3-9]\d{9}', s))

print(is_phone('13800138000'))
print(is_phone('1380013800'))
print(is_phone('13800138000 '))

输出:

输出
True
False
False

3. 找出所有邮箱

Python
import re
print(re.findall(r'[\w.]+@[\w.]+\.[a-z]{2,}', '注册邮箱 tangyuan@example.com 或 a@b.cn'))

输出:

输出
['tangyuan@example.com', 'a@b.cn']

4. 把日期里的斜杠换成短横

Python
import re
print(re.sub(r'(\d{4})/(\d{2})/(\d{2})', r'\1-\2-\3', '2026/10/04 开学'))

输出:

输出
2026-10-04 开学

5. 多个连续空格压成一个

Python
import re
print(re.sub(r'\s+', ' ', '杨枝甘露    18      少冰'))

输出:

输出
杨枝甘露 18 少冰

6. 隐藏手机号中间四位

Python
import re
print(re.sub(r'(\d{3})\d{4}(\d{4})', r'\1****\2', '13800138000'))

输出:

输出
138****8000

7. 用命名组解析一段「日期+温度」

Python
import re
m = re.search(r'(?P<date>\d{4}-\d{2}-\d{2})\s+(?P<temp>-?\d+℃)', '2026-10-04 19℃')
print(m.groupdict())

输出:

输出
{'date': '2026-10-04', 'temp': '19℃'}

8. 用 findall + float 把价格加总

Python
import re
text = '薯片 5.5元,奶茶 12.8元'
print(round(sum(float(x) for x in re.findall(r'\d+\.\d+', text)), 1))

输出:

输出
18.3

9. 忽略大小写地统计关键词出现次数

Python
import re
text = 'Python 很好用,python 也很好用,PYTHON 最常用'
print('出现了', len(re.findall(r'python', text, re.I)), '次')

输出:

输出
出现了 3 次

10. 只保留中文字符

Python
import re
print(re.findall(r'[\u4e00-\u9fa5]+', 'Name:芋圆 Age:18'))

输出:

输出
['芋圆']

十一、常见问题

Q:正则难吗?要背多少?
不用背。只需要记住三件事:\d 是数字、+ 是「一个或多个」、() 是分组。其余全部现场查第三节的表。写了十几个模式之后,常用的那些自然就记住了。

Q:search、match、fullmatch 怎么选?
一句话:整串必须完全符合 → fullmatch;只要里面出现就算 → search;必须从开头算起 → match。校验类需求(手机号、学号、日期格式)基本都用 fullmatch。

Q:为什么我的 .* 把整段都吃掉了?
因为量词默认贪婪。在量词后加 ? 变成 .*? 就是「吃到最近的那个就停」。判断标准是:你要「每一段分别取」还是「整段一次取」。

Q:正则能不能用来解析 HTML / JSON?
不要。这些结构有嵌套,正则处理不了嵌套(这不是你写得不够好,是正则本身的能力边界)。JSON 用 json 库,表格用 csv 库,HTML 用专门的解析库。正则只适合「从一坨文本里捞固定形状的片段」。

Q:模式写得太长看不懂怎么办?
分段写、分段测。先用 print(re.findall(...)) 把一小段模式调通,再往外套。另外把模式存进变量或 compile() 并起个名字(phone、email),比在代码里直接撒一串符号清楚得多。

Q:这个和「网络数据采集」有什么关系?
关系很大——网页拿回来是一大坨 HTML 文本,从中捞出标题、价格、日期这类固定形状的片段时,正则是最快的工具。学完这一篇再去看数据采集,会顺手很多。

AI 助手小贴士|正则是「写了就忘」的典型,AI 是最好的正则解释器——让它写完逐段讲解,再配测试用例。

打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:写一个匹配中国大陆手机号的正则表达式,逐段解释每个符号的含义,再给 5 个测试用例,其中包含 2 个应该匹配失败的例子

十二、随堂自测(12 题)

点击选项即可判分,答错的题会立刻显示解析。

已答 0 / 12 · 正确 0 —

1re.findall(r'\d+', 'a1b22c333') 的结果是?

  • A. [1, 22, 333]
  • B. ['1', '22', '333']
  • C. ['1', '2', '2', '3', '3', '3']
  • D. 6
答案:B。\d+ 是「一个或多个数字」,所以连续的数字会被当成一整段。findall 返回的永远是字符串列表——要算数得自己 int() 转。答案 A 里的元素是数字类型,那得靠推导式转出来。

2re.search() 和 re.match() 的区别是?

  • A. 两者完全一样
  • B. search 从头找,match 从任意位置找
  • C. search 从任意位置找,match 只从开头匹配
  • D. match 只能匹配数字
答案:C。方向正好和直觉相反,这是最常搞混的一组。re.match(r'\d+', 'abc123') 会返回 None,因为开头是字母;re.search 则能找到 123。要「整串完全匹配」应该用 fullmatch。

3re.findall(r'<.*>', '<a><b>') 的结果是?

  • A. ['<a>', '<b>']
  • B. '<a><b>'
  • C. []
  • D. ['<a><b>']
答案:D。.* 默认是贪婪的,会一路吃到文本里最远的那个 >,所以整串被一次匹配完。想分成两段就得加 ? 写成 <.*?>,让它「吃到最近的 > 就停」。

4正则模式 r'\d{3}' 在文本 '12 123 1234' 里会匹配到什么?

  • A. ['123']
  • B. ['123', '123']
  • C. ['12', '123', '1234']
  • D. 什么都匹配不到
答案:A。{3} 是「恰好三个数字」。12 不够三个、不匹配;1234 里只有前三个数字组成一次匹配,多出来的第四个不参与。想要「至少三个」得写 \d{3,}。

5代码里出现 re.findall('\d+', text)(没有 r 前缀),会有什么后果?

  • A. 语法错误,程序根本不能运行
  • B. 能跑,但解释器会给一句 SyntaxWarning: invalid escape sequence '\d'
  • C. 结果和加了 r 完全一样,也没有任何提示
  • D. 自动变成 \\d+,结果不同
答案:B。普通的 '\d' 在 Python 里是无效转义,解释器会警告。多数情况结果碰巧一样,但有些模式会被悄悄改掉意思,很难查。处理办法就一条:所有正则串一律加 r 前缀。

6re.search(r'\d+', '这段文字里没有数字').group() 会怎样?

  • A. 返回空字符串
  • B. 返回 None
  • C. 报 AttributeError: 'NoneType' object has no attribute 'group'
  • D. 自动返回文本里最近的数字
答案:C。search 找不到时返回 None,紧接着调 .group() 就报 AttributeError。正确写法是先判断:if m: 再取 m.group();或者直接用 findall(找不到时返回空列表,不会报错)。

7用命名组解析日期,取值应该怎么写?

  • A. m.group(7)
  • B. m.get('year')
  • C. m.year
  • D. m.group('year') 或 m.groupdict()
答案:D。命名组的写法是 (?P<名字>模式),取值用 group('名字'),一次全取用 groupdict()。返回值是字符串,要算数还得转 int()。组一多,用名字比数编号可靠得多。

8把 2026/10/04 改成 2026-10-04,正确的写法是?

  • A. re.sub(r'(\d{4})/(\d{2})/(\d{2})', r'\1-\2-\3', text)
  • B. re.sub(r'(\d{4})/(\d{2})/(\d{2})', '-', text)
  • C. re.findall(r'/.*-', text)
  • D. re.match(r'\d{4}', text)
答案:A。用括号把三段分别圈起来,替换串里用 \1 \2 \3 把组放回去,就完成了「换分隔符」的操作。B 会把整段日期替换成一个减号,C 和 D 根本不做替换。

9re.findall(r'(\d{3})-(\d{4})', '010-1234') 返回什么?

  • A. ['010-1234']
  • B. [('010', '1234')]
  • C. ['010', '1234']
  • D. ['1234']
答案:B。模式里有几个组,findall 返回的就是组的元组:一个组返回字符串,多个组返回元组,整段反而拿不到了。不想要这个效果,把不关心的组写成 (?:...)。

10re.match(r'\d+', 'abc123') 返回 None,想找到里面的 123 应该改成?

  • A. re.match(r'\d+', 'abc123', re.I)
  • B. re.match(r'.*\d+', 'abc123'),这样最规范
  • C. re.search(r'\d+', 'abc123')
  • D. re.findall(r'\d', 'abc123')
答案:C。match 只认开头,开头是 a 自然匹配不到;换成 search 就能从任意位置找。B 虽然能匹配上,但 .* 会吞掉前面的内容,取 group() 得到的是 'abc123' 而不是 '123'。

11判断用户输入的字符串是不是 11 位手机号,「整串必须完全符合」,应该用哪个函数?

  • A. re.fullmatch
  • B. re.match
  • C. re.search
  • D. re.findall
答案:A。校验格式要用 fullmatch:整串必须完全符合才算通过。search / match 只要一部分匹配上就会返回结果,会出现 '13800138000abc' 也被判为合法这种漏洞。另外记得先 .strip() 去掉首尾空格。

12想「忽略大小写」地查找,应该怎么做?

  • A. 在模式末尾写 !
  • B. 只能靠 text.lower() 预处理,re 不支持
  • C. 把模式整个写成大写
  • D. 加上 re.I 参数,如 re.findall(r'python', text, re.I)
答案:D。re.I(ignore case)忽略大小写,re.M 让 ^ $ 按行匹配,re.S 让 . 也能匹配换行。多个 flag 用竖线连:re.I | re.M。预处理 lower() 也能用,但不是唯一办法。