Python re模块与正则表达式入门指南
正则表达式的实际用途
很多网站或应用在用户输入手机号、邮箱等信息时,会实时验证格式是否正确。这种功能背后就是正则表达式在起作用。
比如,一个手机号校验需求:必须是11位纯数字,且以13、15、17、18或19开头。用纯Python代码实现如下:
while True:
phone = input('请输入手机号: ').strip()
if len(phone) == 11:
if phone.isdigit():
if phone.startswith('13') or phone.startswith('15') or \
phone.startswith('17') or phone.startswith('18') or phone.startswith('19'):
print('手机号合法')
else:
print('开头不符合要求')
else:
print('必须为纯数字')
else:
print('长度必须为11位')
而使用正则表达式配合Python的re模块,代码会简洁很多:
import re
phone = input('请输入手机号: ')
if re.match(r'^(13|14|15|18|19)[0-9]{9}$', phone):
print('手机号合法')
else:
print('手机号不合法')
正则表达式是一种独立的技术,几乎所有的编程语言都支持它。核心思想是用特殊符号的组合来描述字符串模式,从而快速筛选出满足条件的子串。
字符组
字符组用方括号[]表示,里面的字符是"或"关系——只要匹配到其中一个就算成功。
| 正则 | 含义 |
|---|---|
[0123456789] | 匹配0-9任意数字(完整写法) |
[0-9] | 匹配0-9任意数字(简写) |
[a-z] | 匹配任意小写字母 |
[A-Z] | 匹配任意大写字母 |
[0-9a-zA-Z] | 匹配数字或大小写字母 |
例如[0-9]匹配字符串'78i2'会得到['7','8','2'],匹配'111'得到['1','1','1']。
特殊符号(元字符)
元字符能匹配一类字符,常用符号如下:
| 元字符 | 匹配内容 |
|---|---|
. | 除换行符外的任意字符 |
\w | 字母、数字、下划线 |
\s | 空白符(空格、制表符等) |
\d | 数字 |
^ | 字符串开头 |
$ | 字符串结尾 |
| | 逻辑或 |
() | 分组 |
[] | 字符组 |
[^...] | 取反字符组 |
注意:^在字符组[]内部表示取反,在外部表示匹配开头。例如[^a]匹配'ab-ab-a'得到['b','-','b','-']。
再看一个结合开头结尾的例子:^海.匹配'海燕海娇海东'只得到'海燕'(从开头匹配);海.$则得到'海东'(从结尾匹配)。
量词
量词跟在字符或字符组后面,控制匹配次数:
| 量词 | 说明 |
|---|---|
* | 零次或多次 |
+ | 一次或多次 |
? | 零次或一次 |
{n} | 恰好n次 |
{n,} | 至少n次 |
{n,m} | n到m次 |
例如李.匹配'李杰和李莲英和李二棍子',得到三个结果['李杰', '李莲', '李二']。而李.*会贪婪地匹配整个字符串,结果为['李杰和李莲英和李二棍子']。
贪婪与非贪婪
默认情况下,量词会尽可能多地匹配字符(贪婪模式)。在量词后加?可以切换为非贪婪模式,即匹配尽可能少的字符。
| 正则 | 待匹配字符串 | 结果 | 说明 |
|---|---|---|---|
<.*> | <script>...</script> | <script>...</script> | 贪婪匹配,从第一个<到最后一个> |
<.*?> | <script>...</script> | <script>和</script> | 非贪婪,遇到第一个>就停止 |
转义符
反斜杠\用于转义特殊字符。例如\n表示换行符,\d表示数字。如果需要匹配字面意义的\n,则需要写\\n。在Python字符串前加r可以避免多次转义。
建议
对于常见的格式校验(如手机号、邮箱),不必从头写正则,直接在网上搜索现成的表达式即可,前人已经造好了轮子。
Python re模块
re模块提供了正则表达式的完整支持。
findall —— 查找所有匹配
import re
result = re.findall(r'e', 'leethon eat apple')
print(result) # ['e', 'e', 'e', 'e']
finditer —— 返回迭代器
result = re.finditer(r'e', 'leethon')
for match in result:
print(match.group()) # 依次输出每个匹配
search —— 查找第一个匹配
result = re.search(r'e', 'leethon')
if result:
print(result.group()) # 'e'
match —— 仅匹配开头
result = re.match(r'e', 'leethon')
print(result) # None,因为开头是'l'
compile —— 预编译正则
pattern = re.compile(r'\d{3}') # 匹配连续三个数字
numbers1 = pattern.findall('23423422342342344')
numbers2 = pattern.findall('asjdkasjdk32423')
print(numbers1, numbers2)
split、sub、subn
# split: 按模式分割
parts = re.split(r'[ab]', 'abcd')
print(parts) # ['', '', 'cd']
# sub: 替换,可指定替换次数
text = re.sub(r'\d', 'H', 'eva3jason4yuan4', count=1)
print(text) # 'evaHjason4yuan4'
# subn: 返回(替换结果, 替换次数)
result = re.subn(r'\d', 'H', 'eva3jason4yuan4')
print(result) # ('evaHjasonHyuanH', 3)
分组与命名
圆括号()用于分组,分组会改变findall的返回行为:
# 分组优先:findall只返回分组内的内容
result = re.findall(r'www.(baidu|oldboy).com', 'www.oldboy.com')
print(result) # ['oldboy']
# 取消分组优先:使用(?:...)
result = re.findall(r'www.(?:baidu|oldboy).com', 'www.oldboy.com')
print(result) # ['www.oldboy.com']
还可以给分组起别名,方便提取:
pattern = re.search(r'www.(?P<site>baidu|oldboy)(?P<suffix>.com)', 'www.oldboy.com')
print(pattern.group()) # www.oldboy.com
print(pattern.group('site')) # oldboy
print(pattern.group(1)) # oldboy
print(pattern.group('suffix')) # .com