本篇学习笔记参考re——正则表达式 所作,不明白之处可以参阅上述的官方文档。
因为是个人学习笔记,所以并不会完全介绍所有的正则表达式,而是查漏补缺性质的.
A.
在正则匹配的时候,对于匹配数量的控制,常用的特殊字符有 * ? + {m,n} 这4种. 但是这些都是属于“贪婪”匹配,也就是说,这些字符会尽可能多的去进行匹配。 而在实际的情形中,我们可能想进行尽可能少的匹配。这时候有一个办法,就是在这4种匹配的后面跟上 ? 符号。 比如下面的例子:
>>> myfunc="__myfunc__"
>>> re.findall(r'_.*',myfunc) #默认为“贪婪”匹配,所以匹配了整个字符串. 然后就没有需要匹配的了.
['__myfunc__']
>>> re.findall(r'_.*?',myfunc) #此时为最小匹配,所以每次匹配的结果只有一个字符‘_', 然后对剩下的字符串继续进行最小匹配,一共获得了4次匹配的结果。
['_', '_', '_', '_']
>>>
B.
[] 该符号的内部表示字符的集合. 其中集合中的任意字符进行匹配。
1). 在该符号的内部,特殊符号都变成了普通字符。
2). 其中 - 在集合内部的头部或者末尾的时候,才表示普通字符。
3). 该集合的内部支持 字符类,比如 [\t] 表示其中一个字符是 tab键的键值
4). 和POSIX 一样,集合内部的 ^ 如果是在首位,那么表示取反,否则是普通字符
5). 如果集合中含有[ , ] 符号,那么用转义符更方便,虽然官方也支持其他的解决办法,个人更乐意用转义符号.
C.
A|B 这个匹配表示逻辑或,但是 只有 A匹配失败的时候才会匹配B,当然如果有更多个表达式需要匹配,那么可以继续用 | 进行连接,但从左到右,只要匹配成功,那么右边的表达式就不会继续进行匹配.
D.
(?...) 这是个扩展标记法 (一个 '?' 跟随 '(' 并无含义)。这种扩展通常并不创建新的组合;简单点理解就是: 对正则表达式设置一些 flag, 不同版本的python 可能略有不同,具体可以用命令查看:
[ i for i in dir(re) if len(i)==1] #我的环境输出为: ['I', 'L', 'M', 'S', 'T', 'U', 'X']
但是在使用 (?...) 这种形式的时候,对应的flag标志都需要小写,只有一个L需要大写,这里原因不清楚,估计是为了更方便的进行识别吧?毕竟大写的i和小写的L在部分字体中是很难通过肉眼区分的.
对于设置正则表达式的flag, 除了使用 (?...) 的方式外,还可以在每个方法中进行指定,比如下列两种方式使用findall方法的结果相同:
>>> re.findall(r"china","China,CHINA,china",re.I) #传递给findall 方法flag参数
['China', 'CHINA', 'china']
>>> re.findall(r"(?i)china","China,CHINA,china") #使用 (?...) 的方式,无需传递给findall 方法对应的flag 参数.
['China', 'CHINA', 'china']
>>>
E.
(?:…) 一般情况下,通过() 实现正则表达式的分组,然后在后面可以通过 \n 的方式进行引用,但是如果不希望分组被引用,那么可以用 (?:...)的方式进行分组,这个非常有用哦!
F.
在对复杂的字符串进行正则匹配的时候,我们可能需要”对匹配到的内容进行提取或者引用“,这时候最好的方式就是对匹配的内容取个名字,然后引用这个名称 就可以了,Python3.6开始就提供了这种实现:
1). 首先对匹配的内容取一个名字,语法为 (?P<Defined_Name>...),其中 ... 表示匹配的内容,Defined_Name就是定义的名称,这个名称根据你的喜好而定,只要这个名称是有效的python标识符就可以.
简单理解就是:
对于你需要定义名称的正则表达式,需要用() 引用起来,然后写成 (?P<Defined_Name>) 的格式就可以了, 在名称定义好之后,那么就可以进行引用了,目前有三种方式来引用:
a. 在同一正则表达式内引用的语法为: (?P=Defined_Name), 例子如下:
>>> s="https://docs.python.org/zh-cn/3.6/library/re.html"
>>> s2="https://docs.python.org/zh-cn/zh-cn/3.6/library/re.html"
>>> p=r'(.*?)//(.*?)/(?P<myregex>.*?)/(?P=myregex).*?html' #这里使用 myregex 作上述的Defined_Name. 在同一个正则表达式中对myregex 的引用方式为: (?P=myregex)
>>> re.search(p,s) #对字符串s, 没有匹配结果
>>> re.search(p,s2) #对字符串s2,有匹配结果。
<_sre.SRE_Match object; span=(0, 55), match='https://docs.python.org/zh-cn/zh-cn/3.6/library/r>
>>>
b. 对于匹配的对象,那么可以用其 group 方法,然后传递 Defined_Name 作为参数,从而实现引用, 这时候 Defined_Name 做为一个参数,必须是字符串。接着看下面的例子:
>>> r=re.search(p,s2) #匹配结果保存到 r 中。
>>> r.group("myregex") #通过group 方法来取 匹配的结果.
'zh-cn'
c. (?P<>) 其实在正则表达式的处理过程中,也是一个有效的分组,所以,我们可以通过 使用分组的方式来引用上述定义了名称的正则表达式,在re.sub方法中,可以用如下的方式:
>>> p
'(.*?)//(.*?)/(?P<myregex>.*?)/(?P=myregex).*?html'
>>> s2
'https://docs.python.org/zh-cn/zh-cn/3.6/library/re.html'
>>> re.sub(p,"\g<myregex>",s2) # 正则表达式p 匹配整个s2字符串,然后这里就是把整个字符串s2 替换为 group: myregex 所匹配的内容.
'zh-cn'
>>> re.sub(p,"\g<3>",s2) #因为myregex group 对应的是第三个分组,所以可以直接用 分组3 来写,结果是一样的。
'zh-cn'
>>>
G.
(?=…) (?!…) (?<=…) (?<!…) , 这4个表达式,个人认为应该划分为同一个组,都有一个共性: 不占用匹配的位置,仅仅限定了匹配的条件,从这一点上看,有点类似 ^ 和 $ 的意味. 我们很容易注意到后面两个表达式中有”小于号“, 这里的小于号表示前向匹配,毕竟他们不占用匹配的位置,因此这个匹配的条件需要指明:究竟是前向匹配还是后向匹配。 所以另外两个没有”小于号”的就属于后向匹配了。至于“=” 和 “!” 自然就是表示匹配和不匹配了. 这样来理解,个人觉得更容易一些,那么让我们看一些例子吧(注意报错):
>>> s="https://www.jianshu.com/writer#/notes/39570469/notes/fakeurl/preview"
>>> re.findall("(?<=.*#/)notes",s)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "E:\Program files\Python\Python36\lib\re.py", line 222, in findall
return _compile(pattern, flags).findall(string)
File "E:\Program files\Python\Python36\lib\re.py", line 301, in _compile
p = sre_compile.compile(pattern, flags)
File "E:\Program files\Python\Python36\lib\sre_compile.py", line 566, in compile
code = _code(p, flags)
File "E:\Program files\Python\Python36\lib\sre_compile.py", line 551, in _code
_compile(code, p.data, flags)
File "E:\Program files\Python\Python36\lib\sre_compile.py", line 160, in _compile
raise error("look-behind requires fixed-width pattern")
sre_constants.error: look-behind requires fixed-width pattern #这里的报错告诉我们“需要固定长度的pattern". 我们修改下试试!
>>> re.findall("(?<=#/)notes",s) #这下就成功匹配到了,匹配的结果只有notes, #/ 就两个字符的长度,所以上面的报错就是指 (?<=...) 中 ... 所代表的必须是固定长度. 并且 #/ 不占用匹配的结果。
['notes']
>>>
>>> re.findall("(?<!#/)notes",s) #验证 (?<!...) 的结果.
['notes']
验证完了前向匹配条件,我们来验证 后向匹配的表达式,注意看下面的测试结果:
>>> s
'https://www.jianshu.com/writer#/notes/39570469/notes/fakeurl/preview'
>>> re.findall("(?=/[0-9]{8})notes",s)
[] #很奇怪,结果居然是空的,不应该是 notes 吗? 想一下 ^ 指定匹配的字符串的开头,那么必须写在字符串的最开始,而 $ 用于指定字符串的结尾条件,那么$必须写在字符串的末尾,所以 (?=...) 作为后向匹配的条件自然要写到 被匹配字符串的后面,那么 我们修改下看看:
>>> re.findall("notes(?=/[0-9]{8})",s) #这下结果就正常了.
['notes']
>>> re.findall("notes(?!/[0-9]{8})",s) #这个结果也是正常的
['notes']
>>>
H.
关于python中的一些控制字符:
\b 和 \B 都是边界控制字符,对于匹配的字符串的相邻字符进行筛选,从而实现更精准的匹配. 在这里涉及到正则表达式中的”word“的概念。
在ansic 编码下,对word的定义是: 数字,字母,以及下划线组成的字符串,其他的字符不属于word的组成部分。 而 \b 表示匹配的字符串其相邻的字符 不能是word的组成,否则不匹配。而\B 则表示,其匹配的字符串的 相邻字符必须是word的组成,否则不匹配,我们看下面的例子:
>>> re.findall(r"(?i)\bchina\b","China,china_,CHINA,China0,0china,MChInAN") #china 单词的相邻字符不能是word的组成.
['China', 'CHINA']
>>> re.findall(r"(?i)\Bchina\B","China,china_,CHINA,China0,0china,MChInAN") #china单词的相邻字符必须是word的组成.
['ChInA']
>>> re.findall(r"(?i)\bchina\B","China,china_,CHINA,China0,0china,MChInAN") #china单词的前面不能是word的组成,而后面必须是word的组成字符.
['china', 'China']
>>> re.findall(r"(?i)\Bchina\b","China,china_,CHINA,China0,0china,MChInAN") #china 单词的前面必须是word的组成,而后面不能是word的组成.
['china']
>>>
\d , \D 这两个就比较简单了,前面表示数字,后面表示非数字.
\w 就表示上述所述的word. \W 就是\w的取反.
其他更多内容,请参考python的官方文档或者帮助,希望读到此片文章的你也可以获益哦!
网友评论