什么时候该用正则表达式
正则表达式是一种用来描述文本模式的小型语言。它很强大,也很容易被滥用。
在动用 re 之前,先想想普通的字符串方法能不能搞定。.split()、.replace()、.startswith()、"target" in text 比等价的正则表达式更快、更易读,也更不容易出错。把正则表达式留给那些确实有规律、但对固定的字符串操作来说又太灵活的模式:电子邮件地址、电话号码、格式已知的日志行、HTML 或 Markdown 片段,以及任何“找出这类东西”的搜索。
基本词汇
正则表达式模式是一个描述你要找什么的字符串。几个基本构件:
.:任意单个字符\d:任意数字(0 到 9)\w:任意“单词”字符(字母、数字、下划线)\s:任意空白字符^:字符串开头$:字符串结尾[abc]:a、b、c 中的任意一个[^abc]:除 a、b、c 之外的任意字符a|b:a 或 b*:前一个元素出现零次或多次+:一次或多次?:零次或一次{3}:正好 3 次{2,5}:2 到 5 次( ... ):分组(捕获其中匹配的内容)
这些足以应付大多数实际场景中的正则表达式。
主要函数
re.search(pattern, text) 在字符串的任意位置查找第一个匹配。返回一个匹配对象或 None:
正则表达式模式请始终使用原始字符串(r"...")。否则 Python 会尝试把反斜杠解释为字符串转义,你得到的模式就和你输入的不一样了。
re.match(pattern, text) 和 search 类似,但只在开头匹配:
大多数时候你需要的是 search。
re.findall(pattern, text) 以列表形式返回所有不重叠的匹配:
注意,findall 返回的是字符串,而不是匹配对象。如果模式中有一个分组,你得到的是分组的内容。如果有多个分组,你得到的是一个元组列表。
捕获分组
模式中的圆括号会捕获其中匹配到的内容:
命名分组通常更清楚:
一旦正则表达式有不止一个分组,给它们命名会让提取数据的代码好懂得多。
用 re.sub 替换
re.sub(pattern, replacement, text) 会替换所有匹配:
这会去掉所有不是数字的字符。替换字符串可以用 \1、\2 等引用捕获的分组;在原始字符串中,\g<1> 的写法更清楚:
你也可以传入一个函数作为替换内容。对于不是简单交换的转换,这很有用:
编译模式以便复用
如果你要多次使用同一个模式(尤其是在循环中),就先编译一次:
编译后的模式提供同样的方法:search、match、findall、sub,只是不需要再把模式作为第一个参数。效率稍高一些,通常也更易读。
标志
常用的修饰符,可以通过 flags= 参数传入,或者用“或”运算组合在一起:
re.IGNORECASE(re.I):不区分大小写的匹配。re.MULTILINE(re.M):^和$在每一行都能匹配,而不只是字符串的边界。re.DOTALL(re.S):.也能匹配换行符(默认不能)。re.VERBOSE(re.X):允许在模式中使用空白和#注释,提高可读性。
re.VERBOSE 对复杂的模式尤其有用:
分多行写、带注释的正则表达式,比晦涩的单行写法好维护得多。
贪婪与非贪婪
量词(*、+、?、{n,})默认是贪婪的:它们会尽可能多地匹配。加上一个 ? 就能让它们变成非贪婪(惰性)的:
贪婪版本捕获了从 <b> 到 </i> 的整段子串,这多半不是你想要的。非贪婪的 .+? 会在第一个 > 处停下。
(顺便提一句:在生产环境中,不要真的用正则表达式解析 HTML。请使用 html.parser 或 BeautifulSoup。上面的例子只是为了演示贪婪匹配。)
一个贴近实际的例子
解析一种简单日志格式中的行:
没几行代码,却能做很多事。
几个好习惯
- 模式始终使用原始字符串。
- 先写能用的最简单的模式,之后再收紧。
- 分组不止一个时,使用命名分组。
- 要复用的模式先编译。
- 正则表达式比普通的字符串方法慢。如果
.split()就能搞定,那就用.split()。
下一步:错误与调试
到这里,处理真实数据的内容就讲完了:文件、JSON、CSV、HTTP、日期和正则表达式。不过,任何真实的程序迟早都会遇到错误,而读懂 Python 的 traceback 是你能学到的最重要的调试技能。最后一章介绍异常,以及你最常遇到的那些具体错误。
常见问题
Python 中的正则表达式是什么?
正则表达式(regex)是一种用来描述文本模式的小型语言。Python 的 re 模块让你可以搜索模式、提取片段和替换匹配的内容。对于简单的字符串操作来说,它有点大材小用,应该先用 .split() 或 .replace() 这样的字符串方法;但在有规律的模式匹配上,它无可替代。
re.match 和 re.search 有什么区别?
re.match 只在字符串的开头进行匹配。re.search 会扫描整个字符串,找到任意位置的第一个匹配。拿不准的时候就用 search,它更符合人的直觉。
正则表达式模式应该总是用原始字符串吗?
是的。正则表达式模式里经常有反斜杠(\d、\s、\b),而 Python 字符串会把它们解释为转义序列。在前面加上 r,写成 r'\d+',就是告诉 Python 按字面意思处理这个字符串,这样正则表达式本身也更易读。