# Python re 模块:一个老码农眼中的正则表达式
最早接触Python的正则表达式模块时,说实话挺头疼的。那时候刚转过来做Python开发,手里拿着在其他语言里用惯了的正则语法,以为换个语言就是换套API,结果发现Python的re模块远不止是“换个包名”那么简单。
它到底是什么
re模块就是Python内置的正则表达式引擎,说白了是一种描述字符串模式的微型语言。用通俗的话讲,我们可以把它理解成一个很挑剔的安检员——你给他一张照片(模式),他去一堆人里(字符串里)找符合的人。有人说正则表达式是“文本处理的神器”,但这神器其实是个双刃剑。
真正用过几年正则的人会明白,这玩意儿如果写得太复杂,就像在写密码——自己写的时候觉得很有道理,一周后看到自己写的正则基本要重新学习一遍。这点在后面我会详细说到。
它到底能做什么
日常工作中,re主要干三件事:检查字符串是否符合某个模式,从字符串中提取符合模式的部分,以及替换掉符合模式的内容。听起来很简单对吧?但实际用起来千变万化。
比如刚开始写Python爬虫那会儿,经常要从网页里抓数据。一个页面上可能有好几个邮箱地址,人眼扫过去能看出来,但让程序找就是另一回事了。这时候re就派上用场了——定义好邮箱的格式模式,让程序自己去匹配。再比如做日志分析的时候,上百万行日志里找出某类错误,用正则一秒钟搞定。
还有一个不太为人注意的用途:可以用正则来校验用户输入的数据格式。早期接手的一个项目,登录模块就是用re写了个简单的匹配规则就把手机号校验搞定了,比if-else写一堆判断条件清爽不少。
怎么使用它
这里说点实操的东西。核心函数就那么几个:re.match()、re.search()、re.findall()、re.sub()。很多人刚开始容易把match和search搞混——match只从字符串开头匹配,search扫描整个字符串找第一个符合的。这个区别别看简单,用错地方会有完全不同的表现。
还有一点很多人容易忽略:预编译正则表达式。如果一段正则要在代码里反复使用,比如在循环里每次都要重新编译,那是非常浪费性能的。一个比较简单的方法就是用re.compile()编译成正则对象,以后直接调这个对象的方法。举个例子:
# 这种写法性能差forlineinlog_file:match=re.search(pattern,line)# 这种写法就好很多pattern_compiled=re.compile(pattern)forlineinlog_file:match=pattern_compiled.search(line)不过说实话,如果不是处理大量数据,这种性能差异其实不太明显。我见过很多人为了省这个性能,把代码写得很绕,结果收益微乎其微。还是要具体问题具体分析。
一些真正在实践中摸索出来的经验
先说一个血的教训:正则表达式永远不要一次性写得太复杂。我现在习惯的做法是,先写最简单的模式,测试通过后再逐步添加细节。比如要匹配一个日期,先匹配YYYY-MM-DD这个基本格式,确认没问题再考虑年份范围、月份有效性这些细枝末节。
还有一个深坑是re.split()。大多数人知道它可以用正则分割字符串,但很少注意到它会保留分组捕获的内容。有时候想按逗号或换行符分割文本,结果发现分割后的列表里多了些奇怪的东西,捣鼓半天才发现是()分组导致的。
个人觉得比较好的做法是,写正则时尽量使用命名分组。比如(?P<year>\d{4})比(\d{4})可读性强多了。拿到匹配结果后直接用match.group('year')而不是match.group(1),代码维护起来轻松得多。刚开始可能觉得多打几个字符麻烦,但过几个月回头看,这个习惯帮我节省了大把时间。
还有一个体会是正则的贪婪与非贪婪模式。默认情况下*和+是贪婪的,会尽可能多地匹配。很多时候因为不明白这一点,匹配结果和预期相差很远。用?改成非贪婪就解决了,比如.*?。这个其实很基础,但遇到没处理好的时候会让人很抓狂。
和其他同类技术的对比
Python的re模块和JavaScript的正则有些类似,但有几个明显的区别。Python支持命名分组,JS的正则一直等到ES2018才加入。还有Python的re.UNICODE标志可以很好地处理多语言文本,这在做国际化项目的时候特别有用。
如果对比Go语言的regexp包,Python的re更强大些,支持零宽断言(lookahead/lookbehind)这类高级功能。但话说回来,过度使用这些特性容易让正则变成一团乱麻。我一般用反向引用比较多,零宽断言用得少——真到了需要零宽断言才能解决的问题,通常会考虑用非正则的方法去解决。
用过一个比较热门的第三方库regex,支持了更多高级特性,比如可变长度的lookbehind。但平衡来看,对于绝大多数日常场景,标准库re已经够用了。为了一个不太用到的特性引入外部依赖,感觉没必要。
还得说说现在流行的“不用正则”的替代方案。像字符串的split()、startswith()、find()这些方法,在日常处理中其实很实用。有时候看到同事把if 'abc' in my_string改成一大堆regex去匹配,就觉得有点过度设计。但另一方面,in操作符和一些内建字符串方法显然替代不了正则。像从日志数据里提取2024-11-20 15:30:45,123 ERROR [MainThread]这种复杂模式的信息,不用正则几乎没法搞。
说到底,re模块是Python文本处理工具箱里的一把利器,但也只是其中一把工具。选择什么工具,取决于具体要做什么活——这是写了十年Python代码以后最深的体会。