正则表达式
在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。可以使用正则表达式来定义字符串的匹配模式,即如何检查一个字符串是否有跟某种模式匹配的部分或者从一个字符串中将与模式匹配的部分提取出来或者替换掉。
正则表达式--->一种模式--->匹配字符串的模式--->复杂的匹配模式
字符集--->[]--->[a-zA-Z0-9_]{6,20}--->\\w{6,20}
Python使用正则表达式的两种方式:
1.不创建正则表达式对象,直接调用函数进行匹配操作
-match
-fullmatch
2.创建正则表达式对象(Pattern),通过给对象发消息实现匹配操作
-compile
1.正则表达式相关概念
用\bhi\b.*\bLucy\b
来找hi
后面不远处跟着的一个Lucy
。
这里,.
是一个元字符(metacharacter),匹配除了换行符以外的任意字符。*
同样是元字符,不过它代表的不是字符,也不是位置,而是数量——它指定*
前边的内容可以连续重复使用任意次以使整个表达式得到匹配。因此,.*
连在一起就意味着任意数量的不包含换行的字符。那么\bhi\b.*\bLucy\b
的意思就是:先是一个单词hi
,然后是任意个任意字符(但不能是换行),最后是Lucy
这个单词。其中\b
也是元字符。
常用字符
符号 | 解释 | 示例 | 说明 | |
---|---|---|---|---|
1 | . |
匹配任意字符 | b.t |
可以匹配bat / but / b#t / b1t等 |
2 | \w |
匹配字母/数字/下划线 | b\wt |
可以匹配bat / b1t / b_t等 但不能匹配b#t |
3 | \s |
匹配空白字符(包括\r、\n、\t等) | love\syou |
可以匹配love you |
4 | \d |
匹配数字 | \d\d |
可以匹配01 / 23 / 99等 |
5 | \b |
匹配单词的边界 | \bThe\b |
|
6 | ^ |
匹配字符串的开始 | ^The |
可以匹配The开头的字符串 |
7 | $ |
匹配字符串的结束 | .exe$ |
可以匹配.exe结尾的字符串 |
8 | \W |
匹配非字母/数字/下划线 | b\Wt |
可以匹配b#t / b@t等 但不能匹配but / b1t / b_t等 |
9 | \S |
匹配非空白字符 | love\Syou |
可以匹配love#you等 但不能匹配love you |
10 | \D |
匹配非数字 | \d\D |
可以匹配9a / 3# / 0F等 |
11 | \B |
匹配非单词边界 | \Bio\B |
|
12 | [] |
匹配来自字符集的任意单一字符 | [aeiou] |
可以匹配任一元音字母字符 |
13 | [^] |
匹配不在字符集中的任意单一字符 | [^aeiou] |
可以匹配任一非元音字母字符 |
14 | * |
匹配0次或多次 | \w* |
|
15 | + |
匹配1次或多次 | \w+ |
|
16 | ? |
匹配0次或1次 | \w? |
|
17 | {N} |
匹配N次 | \w{3} |
|
18 | {M,} |
匹配至少M次 | \w{3,} |
|
19 | {M,N} |
匹配至少M次至多N次 | \w{3,6} |
|
20 | | |
分支 | foo|bar |
可以匹配foo或者bar |
21 | (?#) |
注释 | ||
22 | (exp) |
匹配exp并捕获到自动命名的组中 | ||
23 | (?<name>exp) |
匹配exp并捕获到名为name的组中 | ||
24 | (?:exp) |
匹配exp但是不捕获匹配的文本 | ||
25 | (?=exp) |
匹配exp前面的位置 | \b\w+(?=ing) |
可以匹配I’m dancing中的danc |
26 | (?<=exp) |
匹配exp后面的位置 | (?<=\bdanc)\w+\b |
可以匹配I love dancing and reading中的第一个ing |
27 | (?!exp) |
匹配后面不是exp的位置 | ||
28 | (?<!exp) |
匹配前面不是exp的位置 | ||
29 | *? |
重复任意次,但尽可能少重复 |
a.*b a.*?b
|
将正则表达式应用于aabab,前者会匹配整个字符串aabab,后者会匹配aab和ab两个字符串 |
30 | +? |
重复1次或多次,但尽可能少重复 | ||
31 | ?? |
重复0次或1次,但尽可能少重复 | ||
32 | {M,N}? |
重复M到N次,但尽可能少重复 | ||
33 | {M,}? |
重复M次以上,但尽可能少重复 |
说明: 如果需要匹配的字符是正则表达式中的特殊字符,那么可以使用
\
进行转义处理,例如想匹配小数点可以写成\.
就可以了,因为直接写.
会匹配任意字符;同理,想匹配圆括号必须写成\(
和\)
,否则圆括号被视为正则表达式中的分组。
2.Python对正则表达式的支持
Python提供了re
模块来支持正则表达式相关操作,下面是re
模块中的核心函数。
函数 | 说明 |
---|---|
compile(pattern, flags=0) |
编译正则表达式返回正则表达式对象 |
match(pattern, string, flags=0) |
用正则表达式匹配字符串 成功返回匹配对象 否则返回None
|
search(pattern, string, flags=0) |
搜索字符串中第一次出现正则表达式的模式 成功返回匹配对象 否则返回None
|
split(pattern, string, maxsplit=0, flags=0) |
用正则表达式指定的模式分隔符拆分字符串 返回列表 |
sub(pattern, repl, string, count=0, flags=0) |
用指定的字符串替换原字符串中与正则表达式匹配的模式 可以用count 指定替换的次数 |
fullmatch(pattern, string, flags=0) |
match 函数的完全匹配(从字符串开头到结尾)版本 |
findall(pattern, string, flags=0) |
查找字符串所有与正则表达式匹配的模式 返回字符串的列表 |
finditer(pattern, string, flags=0) |
查找字符串所有与正则表达式匹配的模式 返回一个迭代器 |
purge() |
清除隐式编译的正则表达式的缓存 |
re.I / re.IGNORECASE
|
忽略大小写匹配标记 |
re.M / re.MULTILINE
|
多行匹配标记 |
说明: 上面提到的
re
模块中的这些函数,实际开发中也可以用正则表达式对象的方法替代对这些函数的使用,如果一个正则表达式需要重复的使用,那么先通过compile
函数编译正则表达式并创建出正则表达式对象无疑是更为明智的选择。
3.实例
例子1:
验证输入用户名和QQ号是否有效并给出对应的提示信息。
"""
要求:用户名必须由字母、数字或下划线构成且长度在6~20个字符之间,QQ号是5~12的数字且首位不能为0
"""
import re
username = input('请输入用户名: ')
qq = input('请输入QQ号: ')
# match函数的第一个参数是正则表达式字符串或正则表达式对象
# match函数的第二个参数是要跟正则表达式做匹配的字符串对象
m1 = re.match(r'^[0-9a-zA-Z_]{6,20}$', username)
if not m1:
print('请输入有效的用户名.')
# fullmatch函数要求字符串和正则表达式完全匹配
# 所以正则表达式没有写起始符和结束符
m2 = re.fullmatch(r'[1-9]\d{4,11}', qq)
if not m2:
print('请输入有效的QQ号.')
if m1 and m2:
print('你输入的信息是有效的!')
提示: 上面在书写正则表达式时使用了“原始字符串”的写法(在字符串前面加上了
r
),所谓“原始字符串”就是字符串中的每个字符都是它原始的意义,说得更直接一点就是字符串中没有所谓的转义字符啦。因为正则表达式中有很多元字符和需要进行转义的地方,如果不使用原始字符串就需要将反斜杠写作\\
,例如表示数字的\d
得书写成\\d
,这样不仅写起来不方便,阅读的时候也会很吃力。
例子2:
从一段文字中提取出国内手机号码。
国内三家运营商的号段分别如下:
电信号段:133/153/180/181/189/177;
联通号段:130/131/132/155/156/185/186/145/176;
移动号段:134/135/136/137/138/139/150/151/152/157/158/159/182/183/184/187/188/147/178
import re
""" match - 从头开始进行匹配
search - 搜索--从任意位置匹配
"""
# 创建正则表达式对象,使用了前瞻和回顾来保证手机号前后不应该再出现数字
pattern = re.compile(r'(?<=\D)1[34578]\d{9}(?=\D)')
sentence = '''重要的事情说8130123456789遍,我的手机号是13512346789这个靓号,
不是15600998765,也是110或119,王大锤的手机号才是15600998765。'''
# 方法一:查找所有匹配并保存到一个列表中
tels_list = re.findall(pattern, sentence)
for tel in tels_list:
print(tel)
print('--------华丽的分隔线--------')
# 方法二:通过迭代器取出匹配对象并获得匹配的内容
for temp in pattern.finditer(sentence):
print(temp.group())
print('--------华丽的分隔线--------')
# 方法三:通过search函数指定搜索位置找出所有匹配
m = pattern.search(sentence)
while m:
print(m.group())
m = pattern.search(sentence, m.end())
说明: 上面匹配国内手机号的正则表达式并不够好,因为像14开头的号码只有145或147,而上面的正则表达式并没有考虑这种情况,要匹配国内手机号,更好的正则表达式的写法是:
(?<=\D)(1[38]\d{9}|14[57]\d{8}|15[0-35-9]\d{8}|17[678]\d{8})(?=\D)
,国内好像已经有19和16开头的手机号了,但是这个暂时不在我们考虑之列。
例子3:
替换字符串中的不良内容
import re
sentence = 'Oh, shit! 你是傻叉吗? Fuck you.'
purified = re.sub('fuck|shit|[傻煞沙][比逼叉缺吊屌碉雕]',
'*', sentence, flags=re.IGNORECASE)
print(purified) # Oh, *! 你是*吗? * you.
说明:
re
模块的正则表达式相关函数中都有一个flags
参数,它代表了正则表达式的匹配标记,可以通过该标记来指定匹配时是否忽略大小写、是否进行多行匹配、是否显示调试信息等。如果需要为flags参数指定多个值,可以使用按位或运算符进行叠加,如flags=re.I | re.M
。
例子4:
拆分长字符串
import re
poem = '窗前明月光,疑是地上霜。举头望明月,低头思故乡。'
sentences_list = re.split(r'[,。, .]', poem)
#去掉列表最后的空字符串
sentences_list = [sentence for sentence in sentences_list if sentence]
for sentence in sentences_list:
print(sentence)