正则表达式解析,让你一次明白正则表达式

正则表达式是利用单个字符来描述、匹配一系列符合某个句法规则字符串的技术。在很多文本编辑器里,正则表达式通常被用来检索、替换那些符合某个模式的文本。在编程项目中,正则也常被用于文本字符串的查找、替换、切分和提取

几乎所有的编程语言都支持正则表达式,但不同的编程语言对正则的规则和定义有所差别,本文对于正则的讨论是基于python语言的

一、五类元字符

元字符就是指那些在正则表达式中具有特殊意义的专用字符,元字符是构成正则表达式的基本元件。正则就是由一系列的元字符组成的,比如d可以表示0-9之间的任意数字,w可以表示字母、数字、下划线中任意字符。正则中的元字符非常多,可分为以下几类:

1、特殊单字符:英文的.表示换行以外的任意字符,d表示任意单个数字,w表示字母、数字、下划线的任意字符,s表示任意的空白字符,相应地D,W,S分别是对对应小写的取反。

正则表达式解析,让你一次明白正则表达式_第1张图片

2、空白符:除了特殊单字符外,你在处理文本的时候肯定还会遇到空格、换行等空白符。其实在写代码的时候也会经常用到,换行符 n,TAB 制表符 t。回车符:光标移动到当前行的开头;换行符:光标”垂直”移动到下一行(并不是移动到下一行的开头,既不改变光标水平位置)。水平制表符:

在文件中的输出显示相当于按下键盘TAB键。一般系统中,显示水平制表符将占8列。同时水平制表符开始占据的初始位 置是第8*n列(第一列的下标为0)。例如: puts("0123456\txx"); puts("0123456t\txx"); 在终端的输出为

垂直制表符(‘\v’) 不常用。它的作用是让‘\v’后面的字符从下一行开始输出,且开始的列数为“\v”前一个字符所在列后面一列。例如: puts("01\v2345"); 在终端输出为

但是竖直制表符在命令提示符中显示不出来,只会显示一个框。

正则表达式解析,让你一次明白正则表达式_第2张图片

s可以表示以上所有空白字符,包括空格

3、量词:特殊单字符和空白符都只能匹配单个字符,但在实际匹配过程中会有出现几次、至少出现几次、最多出现几次等规则,这时候就需要用到量词,正则中的量词元字符主要有六种:*表示0次或多次,+表示1次或多次,?表示0次或1次,{m}m次,{m,}至少m次,{m,n}m到n次

正则表达式解析,让你一次明白正则表达式_第3张图片

4、范围:之前所有元字符都只能匹配单个字符或者单字符的重复,有时需要对完整字符串(比如匹配good或者well等表示好的单词)进行匹配或者多个不同单字符(比如匹配元音aeiou),这时候就用到了范围元字符,范围元字符主要四种:| 或运算符,可匹配前后两个字符串中的任意一个可用于字符串的匹配,比如ab|bc 可匹配文本中的ab和bc,[....]中括号 [] 代表多选一,可以表示里面的任意单个字符,所以任意元音字母可以用 [aeiou] 来表示,中括号中的中划线可表示范围[a-z]可表示a到z的任意字母,如果中括号第一个是脱字符(^),那么就表示非,表达的是不能是里面的任何单个元素。

正则表达式解析,让你一次明白正则表达式_第4张图片

5、断言:断言又称锚点,用来限定字符出现的位置,比如要替换tom字符串为jerry,如果不用断言那么tomorrow的前三个字符也会被替换,这显然是错误的。断言主要分为三类

替换前:tom asked me if I would go fishing with him tomorrow.
替换后:jerry asked me if I would go fishing with him jerryorrow.
复制代码

**5.1单词边界:**b 来表示单词的边界,注意\b限定的是单词的边界。\btom\b代表的是单词tom,而不是以tom开头,且以tom结束的某个字符串。

正则表达式解析,让你一次明白正则表达式_第5张图片

利用断言来就可以完成以上替换案例案例了

str =" tom asked me if I would go fishing with him tomorrow."
re.sub(r"btomb","jerry",str)
替换后:jerry asked me if I would go fishing with him tomorrow.
复制代码
import re
str = "abcab"
res = re.sub(r"\bab\b","de",str)
print(res)
>>abcab   #没有替换成功的原因是str的内容是“abcab”,这5个字母是一个整体。如果str的内容是"ab",就可以替换成功了。

import re
str = "ab"
res = re.sub(r"\bab\b","de",str)
print(res)
>>de

import re
str = "abcab"
res = re.sub(r"ab","de",str)
print(res)
>>decde

5.2行的开始/结束:行的开头和结尾有两套元字符^和$,A和Z,两者的区别是当匹配为多行模型【后面会有介绍】时,前者匹配的是每一行的开头和结尾,而后者一直匹配整个字符串的开头和结尾当不指定多行匹配模式时,^和\A一样,只匹配整个字符串的开头;$和\Z一样,只匹配整个字符串的结尾,从结尾最后一个字符开始匹配。(?m)多行匹配模式;(?s)单行匹配模式;(?i)忽略大小写。(?m)延伸:

(?im-nsx)
im表示应用“i”参数和“m”参数
-nsx表示不应用“n”参数,“s”参数和“x”参数
具体解释:
i参数控制是否忽略大小写,也就是Ignore
Case
例如:
(?i)a可以匹配a和A,因为应用了i参数忽视大小写
(?-i)A只能匹配A不能匹配a,因为去掉了i参数
(?i)a((?-i)a)可以匹配Aa和aa不能匹配AA和aA
这样设置的参数的优先级高于外部程序,所以可以做出部分忽略大小写(或其他)的效果
-
m参数为Multiline,控制^和$是否能匹配行首和行尾
n参数为Named
Group,控制是否只补获命名分组
s参数为Single
Line,控制点号.是否能匹配换行符号.*匹配全文
x参数为Ignore
Whitespace,控制是否忽视正则表达式中的空格和换行(且开启注释功能)

正则表达式解析,让你一次明白正则表达式_第6张图片

正则表达式解析,让你一次明白正则表达式_第7张图片

上图中的‘^\d{6}$’,可以简单地理解为在开头符和结尾符中间只有6位数字,即待匹配的字符串只有6位,且全部是数字。

5.3 环视又称零款断言:在一些场景下需要对要匹配的字符串左右做限定,这就用到了环视。比如我们需要对11位的电话号码做匹配时,12位数字的前11位也会被匹配上,22位数也会被匹配前11位,此时就需要用到环视,即左右都不能是数字。

正则表达式解析,让你一次明白正则表达式_第8张图片

正则表达式解析,让你一次明白正则表达式_第9张图片

对于环视可能上边的表有点复杂,其实本质就是左尖括号代表看左边,没有尖括号是看右边,感叹号是非的意思

正则表达式解析,让你一次明白正则表达式_第10张图片

二、两种量词匹配方式

上一个模块已经介绍了六种量词元字符,其实{m,n}这种形式完全可以替代*,+,?,其中+和*因为有无穷多的属性,需要引进贪婪匹配和非贪婪匹配,先来看一个例子

正则表达式解析,让你一次明白正则表达式_第11张图片

+号的匹配比较好理解,*号的匹配会匹配上四个空,是因为是匹配0次或者多次。针对无穷次匹配属性就引入了贪婪模型:尽可能长的匹配,正则默认就是贪婪模式和非贪婪模式,尽可能少的去匹配,非贪婪模式就是在+或者*后面加个?就可以了。

正则表达式解析,让你一次明白正则表达式_第12张图片

三、四种匹配模式

所谓匹配模式就是改变元字符本身匹配行为的方式,具体分为四类:

1、忽略大小写模式,(?i)比如 不区分 大小写匹配a,python中提供了re.IGNORECASE参数也可以实现忽略大小写功能

正则表达式解析,让你一次明白正则表达式_第13张图片

2、.点号通配模式,也称为单行模式(?s).号本身是匹配换行以外的任意字符的,利用此模式可以使.号匹配任意字符功能相当于[Ww]

正则表达式解析,让你一次明白正则表达式_第14张图片

3、多行匹配模式,通常情况下,^ 匹配整个字符串的开头,$匹配整个字符串的结尾。多行匹配模式改变的就是 ^ 和$匹配整个字符串首尾的方式。这在之前断言中介绍过,(?m)实现多行模式,多行模式匹配每一行的开头结尾,这在日志分析中识别每条以时间开始的日志行非常有用

正则表达式解析,让你一次明白正则表达式_第15张图片

4、注释模式,即为正则表达式添加注释,便于后期维护与复盘(?#comment)。用法:re.findall(r"(?#注释)\w\Z", str)或者re.findall(r"\w\Z(?#注释)", str)

四、小括号的作用

()小括号可以说是正则中使用频率最高,功能最强大的一类符号,那正则中的效果好具体有哪些功能呢?

正则表达式解析,让你一次明白正则表达式_第16张图片

除了分组引用以外,所有内容前面均已讲过了,这里主要介绍分组引用功能。

1、分组与编号

括号在正则中可以用于分组,被括号括起来的部分 “子表达式” 会被保存成一个子组。那分组和编号的规则是怎样的呢?其实很简单,用一句话来说就是,第几个括号就是第几个分组。这么说可能不好理解,我们来举一个例子看一下。这里有个时间格式 2020-05-10 20:23:05。假设我们想要使用正则提取出里面的日期和时间。

正则表达式解析,让你一次明白正则表达式_第17张图片

2、不保存分组

在括号里面的会保存成子组,但有些情况下,你可能只想用括号将某些部分看成一个整体,后续不用再用它,类似这种情况,在实际使用时,是没必要保存子组的。这时我们可以在括号里面使用?: 不保存子组。如果正则中出现了括号,那么我们就认为,这个子表达式在后续可能会再次被引用,所以不保存子组可以提高正则的性能。除此之外呢,这么做还有一些好处,由于子组变少了,正则性能会更好,在子组计数时也更不容易出错。那到底啥是不保存子组呢?我们可以理解成,括号只用于归组,把某个部分当成 “单个元素”,不分配编号,后面不会再进行这部分的引用

正则表达式解析,让你一次明白正则表达式_第18张图片

3、括号嵌套

前面讲完了子组和编号,但有些情况会比较复杂,比如在括号嵌套的情况里,我们要看某个括号里面的内容是第几个分组怎么办?不要担心,其实方法很简单,我们只需要数左括号(开括号)是第几个,就可以确定是第几个子组。

正则表达式解析,让你一次明白正则表达式_第19张图片

4、命名分组

前面我们讲了分组编号,但由于编号得数在第几个位置,后续如果发现正则有问题,改动了括号的个数,还可能导致编号发生变化,因此一些编程语言提供了命名分组(named grouping),这样和数字相比更容易辨识,不容易出错。命名分组的格式为 (?P < 分组名> 正则)。

5、后向引用

在知道了分组引用的编号 (number)后,python中,我们就可以使用 “反斜扛 + 编号”,即 number 的方式来进行引用

正则表达式解析,让你一次明白正则表达式_第20张图片

** 五、四类正则转义场景 **

1、转义字符:反斜杠是python中的转义字符,后的字符就会改变字符本来的意思

正则表达式解析,让你一次明白正则表达式_第21张图片

2、字符串转义和正则转义

可能所有的编程教程中都讲过在正则中要表示反斜杠需要用四个反斜杠,估计有很多程序员可能都不知道底层的原理。从输入字符串到 最终的正则表达式经历了两次转义过程。其中可以使用r避免字符串转义,即用原生字符串进行匹配。避免字符串转义的方式是在字符串前面加上r。

正则表达式解析,让你一次明白正则表达式_第22张图片

import re
str = 'this is lily \ apple'
par = "\\\\"
res = re.findall("\\\\",str)
print(res)

打印结果是["\\"]

正则匹配过程中,经历了两个步骤:首先字符串转义,将"\\\\"转化成"\\";之后"\\"经过正则转义,变成"\"。最后用"\"去匹配字符串str。匹配的结果是["\\"],注意这个结果中的第一个\是按转义字符使用的。

3、正则中的元字符转义

如果现在我们要查找比如星号(*)、加号(+)、问号(?)本身,而不是元字符的功能,这时候就需要对其进行转义,直接在前面加上反斜杠就可以了

4、括号的转义

在正则中方括号 [] 和 花括号 {} 只需转义开括号,但圆括号 () 两个都要转义

res = re.findall("\(\)\[]\{}","()[]{}")
print(res)

打印结果是:["()[]{}"]

5、字符组中的转义

以上描述了元字符的转义,字符组中的转义有三种情况

5.1脱字符在中括号中,且在第一个位置,需要转义。转义前代表非。不在第一个位置,就不需要转义

>>> import re
>>> re.findall(r'[^ab]', '^ab')  # 转义前代表"非"['^']
>>> re.findall(r'[\^ab]', '^ab')  # 转义后代表普通字符['^', 'a', 'b']
>>> re.findall(r'[a^b]', '^ab')  # ['^', 'a', 'b']
复制代码

5.2中划线在中括号中,且在中间位置,需要转义。转义前代表范围。

>>> import re
>>> re.findall(r'[a-c]', 'abc-')  # 中划线在中间,代表"范围"['a', 'b', 'c']
>>> re.findall(r'[a\-c]', 'abc-')  # 中划线在中间,转义后的['a', 'c', '-']
>>> re.findall(r'[-ac]', 'abc-')  # 在开头,不需要转义['a', 'c', '-']
>>> re.findall(r'[ac-]', 'abc-')  # 在结尾,不需要转义['a', 'c', '-']
复制代码

5.3右中括号在中括号中,且在中间或者末尾,需要转义。转义前代表匹配的是]前的字符后面跟上]后的字符+]

​>>> import re
>>> re.findall(r'[]ab]', ']ab')  # 右括号不转义,在首位[']', 'a', 'b']
>>> re.findall(r'[a]b]', ']ab')  # 右括号不转义,不在首位[]  # 匹配不上,因为含义是 a后面跟上b]
>>> re.findall(r"[a]b]","]ab]")  # 右括号不转义,不在首位['ab]']  
>>> re.findall(r'[a\]b]', ']ab')  # 转义后代表普通字符[']', 'a', 'b']
复制代码

6、字符组中其他的元字符

一般来说如果我们要想将元字符(.+?() 之类)表示成它字面上本来的意思,是需要对其进行转义的,但如果它们出现在字符组中括号里,可以不转义。这种情况,一般都是单个长度的元字符,比如点号(.)、星号()、加号(+)、问号(?)、左右圆括号等。它们都不再具有特殊含义,而是代表字符本身。但如果在中括号中出现 d 或 w 等符号时,他们还是元字符本身的含义。经验证,在Python2和3中,如果在中括号中出现 d 或 w 等符号时,他们只代表字符本身,要想代表特殊含义,还是需要转义\。

>>> import re
>>> re.findall(r'[.*+?()]', '[.*+?()]')  # 单个长度的元字符 ['.', '*', '+', '?', '(', ')']
>>> re.findall(r'[d]', 'd12')  # w,d等在中括号中还是元字符的功能['1', '2']  # 匹配上了数字,而不是反斜杠和字母d
复制代码

六、一个方法论

正则表达式的一个方法论:某个位置上可能有多个字符的话,就⽤字符组。某个位置上有多个字符串的话,就⽤多选结构,即re1|re2。出现的次数不确定的话,就⽤量词。对出现的位置有要求的话,就⽤锚点锁定位置。

你可能感兴趣的:(python)