import re
0、前言
本篇笔记基于菜鸟教程以及该知乎教程,融入了自己的一些学习心得。
1、正则表达式模式
高亮处是我的补充,因为根据实际情况确实是能匹配到的
这边我就偷点懒了哈,直接截的是菜鸟教程的图。
下面这一段大家看的时候要特别仔细🤭,\w有时候是大写\W
(非数字字母下划线汉字),有时候是小写\w
(数字字母下划线汉字)。
对\b
和\B
做进一步的说明,即不仅仅单词是有边界的,任意字符(串),都可以视作有边界,边界位于\w
和\W
发生改变的地方,并且边界是属于前(后)面的字符串的最后一个(第一个)字符,对于单词,比如'Hello world!'
,在o处,o是属于\w
的,后面是空格,属于\W
,所以o就是边界;在d处,d是属于\w
的,后面是感叹号,属于\W
,所以d也是边界。
再比如'What happened ??? Wow!'
显然d是happened的边界(d后是空格),我们再来看看对于\W
的。第三个?
是边界吗?如果你确实这么认为,那么你确实错了,其实第三个问号后的空格才是边界,因为空格是属于\W
的,而空格的后面是W,W是属于\W
的,所以空格才是边界!
Talk is cheap. Show me the code!
>>>re.findall(r'\w\W\b', '09 8? 7w c_ 9q p@')
['9 ', 'w ', '_ ', 'q ']
>>>re.findall(r'\w\W\b', '09 8? 7w c_ 9q p@')
['9 ', 'w ', '_ ']
我只在第个命令多输了一个空格,但是结果就不一样了为什么呢?这两个要找的的都是末尾是\w\W
的匹配项。我们从q开始看,第一个命令,q属于\w
,q后面的空格属于\W
,均符合条件,那么空格是不是边界呢,是的!因为空格后面的p是属于\w
的,所以'q_'
被纳入了列表之中。
再来看第二条命令,此时q后面的第一个空格就不是边界了,因为第一个空格后面还有第二个空格,所以'q_'
没有出现在列表里。
而\B
则正好跟\b
相反,可以认为得到补集,不赘述了。
2、正则表达式修饰符 - 可选标志
2.1、re.IGNORECASE
(re.I
)
虽然第1节是常量,但我们必须先简要提一下re.findall
这个函数,因为它是贯穿这一节的函数。
re.findall(pattern, string, flag=0)
: 从字符串任意位置查找,返回一个列表。pattern
是欲匹配的字符(串),string
是查找源,flag
是修饰符,默认是0
re.I
的作用是忽略字符大小写
text = "I'm Jasmine-Feng. My student number is No. 321432"
pattern = r"Jasmine-FENG"
print('Default: ', re.findall(pattern,text))
print('Ignore upper/lower case: ', re.findall(pattern,text,flags=re.I))
N.B. pattern被赋了一个r字符串,这个r字符串的作用是避免转义,r是raw的缩写,也就是保持原样的意思。可看这篇博文。一般来说,使用正则表达式都会用到这个r字符串。
Default: []
Ignore upper/lower case: ['Jasmine-Feng']
Process finished with exit code 0
在默认情况下,区分大小写,找不到ENG;若不区分,则可以找到eng。
2.2、re.ASCII
(re.A
)
re.A
的作用是只匹配ASCII码支持的字符,那么具体指哪些字符呢?下图来自百度百科。
汉字是不在这个里面的,所以如果修饰符是re.A
的话就匹配不了汉字了哈~
text = "我是Jasmine-Feng. 我的学号是No. 321432"
pattern = r"\w+"
print('Default: ', re.findall(pattern,text))
print('ASCII: ', re.findall(pattern,text,flags=re.A))
\w+
的作用是匹配一个或多个字母数字下划线汉字
Default: ['我是Jasmine', 'Feng', '我的学号是No', '321432']
ASCII: ['Jasmine', 'Feng', 'No', '321432']
Process finished with exit code 0
2.3、re.DOTALL
(re.S
)
在正则表达式模式中,.
是用来
text = "我\t是Jasmine-F\neng. 我%的◉学号是No. 321432"
pattern = r'.*'
print('Default: ', re.findall(pattern,text))
print('DOTALL: ', re.findall(pattern,text,re.S))
.*
的作用是匹配长度至少为0的字符(串),emmm,好像是句废话?事实上,只要整段话不被换行符截断,就可以得到整个字符串(外加一个空字符串)。
Default: ['我\t是Jasmine-F', '', 'eng. 我%的◉学号是No. 321432', '']
DOTALL: ['我\t是Jasmine-F\neng. 我%的◉学号是No. 321432', '']
Process finished with exit code 0
2.4、re.MULTILINE
(re.M
)
$
匹配定位到字符串末尾,^
定位到字符串开头,默认情况下,如果换行,是不能定位到新一行的行头/尾的,而用re.M
修饰则可以,也就是多行模式。
text = "我\t是Jasmine-F\neng. 我%的◉\n学号是No. 321432"
pattern = r'.$'
pattern2 = r'^.'
print('Default, end: ', re.findall(pattern, text))
print('MULTILINE, end: ', re.findall(pattern, text, re.M))
print('Default, start: ', re.findall(pattern2, text))
print('MULTILINE, start: ', re.findall(pattern2, text, re.M))
Default, end: ['2']
MULTILINE, end: ['F', '◉', '2']
Default, start: ['我']
MULTILINE, start: ['我', 'e', '学']
Process finished with exit code 0
2.5、re.VERBOSE
(re.X)
verbose是“详实的、冗长的”意思,通过该修饰符可以在正则表达式中加入注释。注意,是往pattern
里面加,不是往text
加!我一开始以为是可以往text
加注释,然后调试半天都得不到结果。。。
text = '朋友们好啊!我是xxxxxx拳掌门人xxx~'
pattern = r'''朋友们 # 主语
好啊! # 谓语
'''
print(re.findall(pattern, text,re.VERBOSE))
['朋友们好啊!']
Process finished with exit code 0
2.6、修饰符的叠加
使用|
可以叠加修饰。
text = 'Hello everybody!\n我是xxxxxx拳掌门人xxx~'
pattern = r'BODY.*$'
print(re.findall(pattern, text, re.I))
print(re.findall(pattern, text, re.M))
print(re.findall(pattern, text, re.M | re.I))
[]
[]
['body!']
Process finished with exit code 0
3、正则表达式函数
3.1、查找单个匹配项的函数
函数 | 功能 |
---|---|
search | 从任意位置开始搜索 |
match | 从开头搜索,不用完全匹配 |
fullmatch | 从开头搜索,必须完全匹配 |
这一类函数返回的都是Match
对象,要用group
方法以获得匹配值
在这边顺便提一下正则表达式模式中的圆括号()
,它的作用是按照常见说法是分组,而根据我的理解,其实就是给他一个标记,方便后面引用,对于findall
finditer
这样的函数来说,pattern
有没有圆括号是无所谓的,而对于search
match
fullmatch
这样返回Match
对象的就有所谓了。
group(1)
返回第一个组,group(2)
返回第二个组,依此类推,当n大于0,group(n)
可以看第n个组;而当n为0或者不给group
输任何参数的话,则返回的是整个匹配项
Example 3.1.0
text = 'Call me 掌门人,call me 掌门人。Call me 掌门人; call me 掌门人!'
pattern = r'([\w\s]+)([^\w\s])([\s\w]+)'
pattern2 = r'[\w\s]+[^\w\s][\s\w]+'
print(re.findall(pattern,text))
print(re.findall(pattern2,text))
print(re.search(pattern,text).group(0))
print(re.search(pattern,text).group(1,2,3))
pattern中分出了中文标点前、中文标点本身、中文标点后三个部分。
[('Call me 掌门人', ',', 'call me 掌门人'), ('Call me 掌门人', ';', ' call me 掌门人')]
['Call me 掌门人,call me 掌门人', 'Call me 掌门人; call me 掌门人']
Call me 掌门人,call me 掌门人
('Call me 掌门人', ',', 'call me 掌门人')
Process finished with exit code 0
其实后面的部分我本来是写了自己的例子的,但是浏览器给我误关了,又没保存(心态直接炸裂😟),所以3.1和3.2两小节从这边开始,我将使用知乎教程的例子。
Example 3.1.1
Example 3.1.2
Example 3.1.3
3.2、查找多个匹配项的函数
函数 | 功能 |
---|---|
findall | 返回含匹配项的列表 |
finditer | 返回含匹配项的迭代器 |
Example 3.2.1
从内存角度来看,迭代器占用内存更少。
如果可能存在大量的匹配项的话,建议使用finditer函数,一般情况使用findall函数基本没啥影响。
3.3、分割
使用到split
方法,参数:re.split(pattern, string, maxsplit=0, flags=0)
其中maxsplit
是最大分割次数,缺省值是0,注意并不是0就不分割了,相反,0是有多少就分割多少,可以理解为一个极限值(正无穷?)。其余参数前文皆已提及。
Example 3.3.1
text = 'Call me 掌门人,call me 掌门人。Call me 掌门人; call me 掌门人!'
pattern = r'[^\s\w]+'
res1 = re.split(pattern,text,maxsplit=0,flags=re.I)
print('split: ', res1)
print(re.findall(pattern,text))
print(re.search(pattern,text))
[^\s\w]+
表示除了(数字字母下划线空格)外的任意一个或多个字符
split: ['Call me 掌门人', 'call me 掌门人', 'Call me 掌门人', ' call me 掌门人', '']
[',', '。', ';', '!']
<re.Match object; span=(11, 12), match=','>
Process finished with exit code 0
3.4、替换
替换要用到sub
函数,基本语法是re.sub(pattern, repl, string, count=0, flags=0)
,其中repl
是替换的新字符(串)或函数,count
是最大替换次数,缺省值是0(同样表示有多少替换多少)。来看个实例。
Example 3.4.1
text = 'Call me 掌门人,call me 掌门人。Call me 掌门人; call me 掌门人!'
pattern = r'([^\w\s])'
repl1 = ','
repl2 = lambda matchobj: ',,' if matchobj.group() in [',',';'] else '..'
print(re.sub(pattern,repl1,text))
print(re.sub(pattern,repl2,text))
在repl1
中,我们把匹配对象替换成了中文的逗号;而在repl2
中,我们把匹配对象替换成了两个英文逗号或两个英文句点。注意matchobj
后面跟上了group
方法,这是因为直接返回的是Match
对象。
Call me 掌门人,call me 掌门人,Call me 掌门人, call me 掌门人,
Call me 掌门人,,call me 掌门人..Call me 掌门人,, call me 掌门人..
Process finished with exit code 0
subn
函数与sub
函数作用基本相同,只是返回值不同,subn
返回的是一个元组,元组是这样构成的:(字符串,替换次数)
。
Example 3.4.2
text = 'Call me 掌门人,call me 掌门人。Call me 掌门人; call me 掌门人!'
pattern = r'([^\w\s])'
repl1 = ','
repl2 = lambda matchobj: ',,' if matchobj.group() in [',',';'] else '..'
print(re.subn(pattern,repl1,text))
print(re.subn(pattern,repl2,text))
print(re.findall(pattern,text))
('Call me 掌门人,call me 掌门人,Call me 掌门人, call me 掌门人,', 4)
('Call me 掌门人,,call me 掌门人..Call me 掌门人,, call me 掌门人..', 4)
[',', '。', ';', '!']
Process finished with exit code 0
Example 3.4.3
如果 repl
中含有反斜杠 \
,则需要使用四个反斜杠来表示:\\\\
text = 'Call me 掌门人,call me 掌门人。Call me 掌门人; call me 掌门人!'
pattern = r'([^\w\s])'
repl = '\\\\'
s = re.sub(pattern, repl, text)
print(s)
'Call me 掌门人\\call me 掌门人\\Call me 掌门人\\ call me 掌门人\\'
f = open('test.txt', 'w')
f.write(s)
f.close()
打开test.txt
查看
3.5、编译正则对象
re.compile(pattern, flags=0)
compile
函数将正则表达式的样式编译成正则表达式对象Pattern
。这个对象与re模块有同样的正则函数。
在下一节中我们讲具体讲解Pattern
对象。
4、正则对象Pattern
在上节中已提及,Pattern
对象可以通过compile
函数编译获得,此正则对象拥有与re
模块相同的函数。
例如,Example 3.4.2可以写成先预编译成Pattern
对象,再调用Pattern
对象的subn
方法。
text = 'Call me 掌门人,call me 掌门人。Call me 掌门人; call me 掌门人!'
pattern = r'([^\w\s])'
pattern_obj = re.compile(pattern)
repl1 = ','
repl2 = lambda matchobj: ',,' if matchobj.group() in [',',';'] else '..'
print(pattern_obj.subn(repl1,text))
print(pattern_obj.subn(repl2,text))
print(pattern_obj.findall(text))
('Call me 掌门人,call me 掌门人,Call me 掌门人, call me 掌门人,', 4)
('Call me 掌门人,,call me 掌门人..Call me 掌门人,, call me 掌门人..', 4)
[',', '。', ';', '!']
Process finished with exit code 0
其实compile函数 与 其他 re函数(search、split、sub等等) 内部调用的是同一个函数,最终还是调用正则对象的函数!
官方文档推荐:在多次使用某个正则表达式时推荐使用正则对象Pattern 以增加复用性,因为通过 re.compile(pattern) 编译后的模块级函数会被缓存!
感谢菜鸟教程和知乎教程!(链接在文首给出)
完。