re.search() 返回 None 主因是未匹配到目标,需判空再调用 .group();re.findall() 行为取决于捕获组:无组返字符串列表,有组返组内内容(单组返字符串列表,多组返元组列表);贪婪匹配需用 .*? 或精确字符集避免过度匹配。

re.search() 返回 None 的常见原因
调用 re.search() 后直接取 .group() 报 AttributeError: 'NoneType' object has no attribute 'group',基本就是没匹配上——它只找第一个,找不到就返回 None,不抛异常。
容易踩的坑是忘了加判空:
- 写成
re.search(r'\d+', text).group(),一旦文本无数字就崩 - 正则里用了
^或$却忽略多行模式:re.search(r'^start', s)在换行后失效,得加re.MULTILINE - 中文、emoji 等 Unicode 字符没加
re.UNICODE(Python 3 默认已启用,但显式写上更稳妥)
正确姿势是先检查结果:
match = re.search(r'\d+', text)
if match:
print(match.group())
re.findall() 返回空列表 vs 返回字符串元组
re.findall() 行为取决于正则里有没有「捕获组」——这是最常被忽略的隐性规则。
没括号时,它返回字符串列表:
re.findall(r'\w+', 'a1 b2 c3') # ['a1', 'b2', 'c3']
有括号(哪怕一个),它只返回括号里的内容,且如果多个括号,返回元组列表:
re.findall(r'(\w)(\d)', 'a1 b2 c3') # [('a', '1'), ('b', '2'), ('c', '3')]
re.findall(r'(\w)\d', 'a1 b2 c3') # ['a', 'b', 'c']
常见误用:想提取邮箱前缀和域名,写了 r'(\w+)@(\w+\.\w+)',结果拿到一堆元组,却忘了 unpack 或索引访问。
立即学习“Python免费学习笔记(深入)”;
贪婪匹配导致跨段落吞掉中间内容
像 .* 这种默认是贪婪的,遇到 .* 就往死里吃,直到最后一个符合条件的边界。比如:
re.search(r'<div>(.*)</div>', '<div>A</div><div>B</div>') # 匹配到的是 'A</div><div>B',不是 'A'
解决方法只有两个:
- 改用非贪婪:把
.*换成.*? - 用更精确的字符集替代点号,比如
[^ 表示“除
注意:非贪婪不是万能解药,嵌套结构(如 HTML 里 div 套 div)正则根本处理不了,该上 BeautifulSoup 就别硬刚。
编译正则对象提升重复使用性能
如果你在循环里反复调用 re.search(r'\d{3}-\d{2}-\d{4}', line),每次都在解析正则字符串,开销不小。
应该提前编译:
ssn_pattern = re.compile(r'\d{3}-\d{2}-\d{4}')
for line in lines:
match = ssn_pattern.search(line) # 快很多
编译后还能复用标志位,比如统一处理大小写:
word_pattern = re.compile(r'python', re.IGNORECASE)
word_pattern.findall('Python and PYTHON') # ['Python', 'PYTHON']
另外,re.compile() 会缓存最近 100 个未编译的正则(CPython 实现),但别依赖这个——显式编译更可控,也方便调试时打日志看 pattern 对象。
真正复杂的地方不在语法,而在你是否意识到:正则只是做「线性扫描+简单切片」,一旦需求涉及上下文判断、嵌套层级或状态累积,它就开始力不从心了。










