一个重要且常见的问题:
写正则表达式时,我们需要在对欲检索的文本的了解程度和检索精准度之间求得平衡。: 越精准,容错性越差;但是容错性越好,越容易出现异常。所以,我们需要匹配所有需要匹配的,同时,忽略掉所有不需要匹配的。好像是句废话,实际是精髓。
一,正则表达式入门:
1. 字符组:
[123] : 1或2或3
[^123] :除123之外的任何字符,这里的字符是指广义字符。包括:~!@#$%^&*()等。
[^1] : 意义是:匹配一个除1之外的字符,而不是不要匹配字符1.
举例,请注意二者的差别:
re.findall('(f[^1])','asdfasdf')
['fa']
re.findall('(f[^1])','asdfasdf\n')
['fa', 'f\n']
[^-k] : 当”-”出现在[或[^之后,元字符”-”失效。
举例:
re.findall('([^-k])','dfdfdf')
['d', 'f', 'd', 'f', 'd', 'f']
2. 多选结构:“|”
‘123|456’ : 123或456
grey|gray : grey 或 gray
举例:
re.findall('grey|gray','graygrey')
['gray', 'grey'] : grey 或 gray
re.findall('gr[ea]y','graygrey')
['gray', 'grey'] : [ea]:e或a
re.findall('gre|ay','graygrey')
['ay', 'gre'] : 'gre’ 或 ’ay’
re.findall('(gr(?:e|a)y)','graygrey')
['gray', 'grey'] : (gr(?:e|a)y) 相当于gr(e|a)y :grey 或 gray
re.findall('gr[e|a]y','graygrey')
['gray', 'grey'] : [e|a] 相当于 e或”|”或a
re.findall('gr[e|a]y','graygreygr|y')
['gray', 'grey', 'gr|y']
注意re.compile(‘^a|b|c’)和re.compile(‘^(a|b|c)’)之间的差别。
前者指’^a’或’b’或’c’。
后者指’^a’或’^b’或’^c。
3. 正则中还有一个“单词锚定”的概念,但python似乎不支持,也许我没找到。
4. 量词? * +
re.compile(‘4(th)?’)等价于re.compile(‘4|4th’) 等价于re.compile(‘4(|th)’)。
re.compile(‘4(th)*’) 若干个th
5. 括号及反向引用
前文所述,括号有两个作用:
a) re.compile(‘^(1|2|3)’) 针对多选项
b) re.compile(‘4(th)?’) 针对量词
括号的第三个作用:反向引用
直接上例子:
re.search(r'(\d)abc\1','1abc1').groups()
('1',)
后面的\1匹配前面括号中匹配的东西”\d”. 有点绕口,在上几个例子:
re.search(r'(\d)abc\1','2abc2').groups()
('2',)
re.search(r'(\d)abc\1','1abc2').groups()
Traceback (most recentcall last):
File "<interactive input>",line 1, in <module>
AttributeError: 'NoneType'object has no attribute 'groups'
没匹配上,出了异常,因为前面是1后面是2. '1abc2'
6. python中反向引用的扩展:
re.compile(r'(?P<kk>\d)abc(?P=kk)').search('1abc1').groups()
('1',)
(?P<kk>\d):给这个group加一个名字<kk>
(?P=kk):获取已匹配的内容。
在来一个例子:
re.compile(r'(?P<asdf>\d)abc(?P=asdf)').search('1abc1').groups()
('1',)
在一个group里面的前两个字符是”?:”,那么这个括号不被正则引擎记入。。。有点绕后,希望这个例子能说明白。
re.compile(r'(?:\d)(\d)abc\1').search('12abc22').groups()
('2',)
例子中的\1 匹配的是第二个括号中的\d而不是第一个。因为第一个group中有一个”?:”被忽略掉了。
这个例子举的不好。因为这样也可以:
re.compile(r'\d(\d)abc\1').search('12abc22').groups()
('2',)
实际上”?:”这个东西主要用在上文中括号的第一和第二个作用(绑定/捆绑),又不想被记入第三个功能(反向引用)的时候使用的。。。希望我说明白了。
小结:
二,扩展
1. 环视(lookaround)
顺序环视 lookahead:
肯定型顺序环视:
逆序环视:
否定性逆序环视:
这块儿有点烦。1,难。2,用的不多。3,但很重要。
举例之前,还是简单介绍一下吧。
前文曾经介绍过元字符^和&.
^表示 待匹配文本的开始
$表示 待匹配文本的结尾
像^,$这种元字符和perl中的\b,叫做锚定位。锚定就是锚一个位置,定住(废话!)。锚定位就是一个位置标识符,但是并不占用待匹配的内容。我举个例子:
re.compile('^qweqweqwe')
这句话的意思是说匹配以字母q开头,后面跟着weqweqwe。^只是一个位置标识符,表示起始位置,并不占用内容。
如果你想匹配” qweqweqwe”。正确。可匹配成功。
如果你想匹配”aqweqweqwe”.错误。因为开头是字母a 不是q。
不知道是否有同学会思考,如果我想匹配”^qweqweqwe”怎么办?就不告诉你~!
上段介绍了锚定的概念是为了引出这节将要介绍的环视。
其实环视我个人的理解就是一个变相的锚定。
举例子:
肯定型顺序环视:
re.search('(?=Linshuhao)Linsh','sjkjsLinshuhao')
<_sre.SRE_Match object at 0x029BA1E0>
匹配成功。
解释一下:
'(?=Linshuhao)Linsh' 这个是正则的定义。我们把它拆开:
(?=Linshuhao) 这个就是上文开头那个表中的第一项:肯定型顺序环视。
Linsh 这个就是想要去匹配的字符了。
'sjkjsLinshuhao' 这个是被匹配的文本。我要拿正则去匹配它。
放一起,解释一遍:
(?=Linshuhao)这个意思是锚定一个位置,哪个位置呢:'sjkjsLinshuhao' 就是我剪头指向的位置。
那么'(?=Linshuhao)Linsh 的意思就是在这个位置从左向右匹配Linsh。
举个反例:
re.search('Linsh','sjkjsLinshaahahahahha')== None
False
False意味着不等于None,意味着匹配成功了。
因为'sjkjsLinshaahahahahha'这个字符串中确实存在Linsh这个字符串。
re.search('(?=Linshuhao)Linsh','sjkjsLinshaahahahahha')== None
True
但是这个失败了。因为(?=Linshuhao),的意思是锚定位置到Linshuhao的起始位置。但是整个文本并没有这个位置。
在举一组例子:
记住,锚定位置!!!
re.search('123(?=Linshuhao)','sdakj123Linshuhao')== None
False
匹配成功。
两种理解方法,一个是官方的,一个是我的。
先说官方的:
首先锚定位置,(?=Linshuhao) 锚定的就是Linshuhao的前端,就是箭头所指的位置:
'sdakj123 Linshuhao'
'123(?=Linshuhao)' 整个放一起的意思就是123后面是那个锚定的位置。哪个锚定的位置?就是Linshuhao的起始位置。也就是箭头指向的那个位置。
我的理解:
'123(?=Linshuhao)' 123后面必须是Linshuhao,别的不匹配。
re.search('123(?=Linshuhao)','sdakj123fsdsaLinshuhao')== None
True
总结:(?=Jeffrey)Jeff 的匹配:
否定型顺序环视:
re.search('123(?=Linshuhao)','sdakj123Linshuhao')== None
False
还拿这个举例子
肯定型顺序环视是指123后面必须是Linshuhao。否定型的意思就是123后面必须不是Linshuhao。
否定型的表达式是这样的:
re.search('123(?!Linshuhao)','sdakj123Linshuhao')== None
True
re.search('123(?!Linshuhao)','sdakj123sdlLinshuhao')== None
False
自己感受下。。。
思考题:
re.search('123(?!=Linshuhao)','sdakj123Linshuhao')== None
True or False?
肯定型/否定型逆序环视:
下面重点介绍逆序环视:
re.search('(?<=Linshuhao)123','Linshuhao123')
<_sre.SRE_Match object at 0x029BA758>
匹配成功了。逆序环视匹配的是'Linshuhao123'
为了方便对比给出顺序环视的锚定位:
re.search('123 (?=Linshuhao) ', ‘123Linshuhao')
'123Linshuhao'
肯定型和否定型大家应该很清楚了。给一个否定型的写法吧。
re.search('(?<!Linshuhao)123','Linshuhao123')
环视的实际应用:
在《精通正则学习笔记1》中,我没有找到python支持的单词锚定元字符。在这里。我们可以应用环视,人为写出,相当于perl的\b的表达式:
1.表示字符的前端的锚定位
re.compile(r'(?<!\w)(?=\w)123')
<_sre.SRE_Pattern object at0x029D04D0>
2.表示字符的后端的锚定位
re.compile(r'123(?<=\w)(?!\w)')
<_sre.SRE_Pattern object at0x029C7340>
3.兼容前端和后端
re.compile(r'(?:(?<!\w)(?=\w))|(?:(?<=\w)(?!\w)'))
第二章,到此结束。
三,正则表达式的特性和流派概览
1. 正则模式和匹配模式
两种表达方式,各举一个例子:
a. re.compile('123123',re.I|re.M)
b. re.search('(?im)123123','123123123123123')
下面分别解释python支持的各个模式的意义:
re.I (ignore case):忽略大小写。
re.L (locale dependent):和re.U对应。一个是local 一个是unicode。本人猜测默认应该是local的。Unicode模式是指,unicode格式,两个字符连一起组成组合表示一个字符。从而影响到\w, \W,\b, \B, \d,\D, \s and \S。例如,\w可以匹配两个字符。猜测是这个意思。以后如果遇到,再说。
re.M (multi-line):多行模式,又叫增强的行锚点模式。呸呸呸。
^和$ 不在这个模式下,只匹配字符串的开始和结束位置。
但在多行模式下,^匹配的是一行的开始位置。同理$匹配一行的结束位置。
举例:
re.findall('(?m)^(\d{3})$','123\n456\n789')
['123', '456', '789']
re.findall('^(\d{3})$','123\n456\n789')
[]
自己体会一下。
多行匹配模式,是所有模式中应用最广泛的。
犹豫在该模式下,^和$只能匹配行首和行尾。字符串的首尾由元字符\A,\Z替换:
\A:指定匹配必须出现在字符串的开头(忽略 Multiline 选项)。
\Z:指定匹配必须出现在字符串的结尾(忽略 Multiline 选项)。
总结一下:
^:行首。&:行尾。\A:总字符串首。\Z:总字符串尾。
自己体会一下。
re.S (dot matches all):点号通配模式。
通常情况下“.”匹配所有字符,除了\n。
但是在点号通配模式下。”.” 匹配所有字符,包括\n.
举例:
这条语句曾经出现在driver management中的*common.py中,但是被扼杀了。
re.search('(?ms).*DeviceDesc%=.+?,(.*?)\s.*[Strings].*DeviceDesc\s*=\s*"(.*?)"\r\n',tmpString).groups()
re.X (verbose):
回车换行,空格都无效。
下面两句话表示一个意思。
a = re.compile(r"""\d + # the integral part
\. # the decimal point
\d * # some fractionaldigits""", re.X)
b = re.compile(r"\d+\.\d*")
2. 元字符+一些关键字总结:
a. 字符组:强调几点。
1.元字符在字符组内/外意义不同。也可能失效。例如[*]只代表”*”并不是量词。[\b]表示退格。\b表示。。。。
2.[^asdasd],字符组中的字符是指广义字符,包括且不限于@#$%^&*()_
3.排除型字符组[^asdasd]也表示仍然需要匹配一个字符。
b. 点号.:在大多数情况下,“.” == [^\n].在多行模式下“.”可以匹配任何字符(广义)。
c. \d \D \w \W \s \S:自己百度。
d. 锚点: ^ $ \A \Z
e. 单词分解符 \b \B ,这里要解释一下,在《1》中曾经说python 不支持单词分解符,其实是支持的。
f. 环视 (?= … ) (?! …) (?<= …) (?<! …)
g. (?: …) 只分组不捕获
h. 命名捕获:两种情况,
1,需成对出现:
re.search('((?P<qwe>\d))kk(?P=qwe)','1kk1').groups()
('1', '1')
re.search('(?P<qwe>\d)kk(?P=qwe)','1kk1').groups()
('1',)
re.search('(?P<qwe>\d)kk((?P=qwe))','1kk1').groups()
('1', '1')
re.search('(?P<qwe>\d)kk(?:(?P=qwe))','1kk1').groups()
('1',)
re.search('(?:(?P<qwe>\d))kk(?:(?P=qwe))','1kk1').groups()
('1',)
re.search('(?:(?P<qwe>\d))kk','1kk1').groups()
('1',)
2,单独出现亦可:
m = re.match(r"(?P<first_name>\w+)(?P<last_name>\w+)", "Malcolm Reynolds")
m.group('first_name')
'Malcolm'
m.group('last_name')
'Reynolds'
re.search('(?P<qwe>\d)kk','1kk1').groupdict()
{'qwe': '1'}
i. 固化分组,和正则引擎的机制有关(第四章介绍),吃进去的东西不会吐出来。
可惜,python不支持,但是可以通过环视模拟。nice!
'(?=(\w+))\1'
感受一下。
j. 多选结构或: “|”
k. 条件判断,针对正则表达式的条件判断:
re.search(r'(?P<tt>\d)asdasd(?(tt)\d|abc)','1asdasd1').groupdict()
{'tt': '1'}
re.search(r'(?P<tt>\d)?asdasd(?(tt)\d|)','rasdasdabc').groupdict()
{'tt': None}
re.search(r'(\d)asdasd(?(1)\d|abc)','1asdasd1').groups()
('1',)
l. 量词 ? * + {1,5}
m. ?? *? +? {1,5} 量词的非贪婪模式。下章介绍。
第三章结束。