python中,命令行和脚本等,里面都会对转义符号作处理,此时的字符串会和正则表达式的引擎产生冲突。即在python中字符串'\n'会被认为是换行符号,这样的话传入到re模块中时便再也不是‘\n’这字面上的两个符号,而是一个换行符。因此,咱们在传入到正则引擎时,必须让引擎单纯的认为是一个'\'和一个'n',因此须要加上转义符成为'\\n',针对这个状况,python中使用raw_input方式,在字符串前加上r,使字符串中的转义符再也不特殊处理(即python中不处理,通通丢给正则引擎来处理),那么换行符就是r'\n' html
. #普通模式下,匹配除换行符外的任意字符。(指定DOTALL标记以匹配全部字符)
* #匹配前面的对象0个或多个。千万不要忽略这里的0的状况。 + #匹配前面的对象1个或多个。这里面的重点是至少有一个。 ? #匹配前面的对象0个或1个。 {m} #匹配前面的对象m次 {m,n} #匹配前面的对象最少m次,最多n次。
^ #匹配字符串开头位置,MULTILINE标记下,能够匹配任何\n以后的位置 $ #匹配字符串结束位置,MULTILINE标记下,能够匹配任何\n以前的位置
\m m是数字,所谓的反向引用,即引用前面捕获型括号内的匹配的对象。数字是对应的括号顺序。 \A 只匹配字符串开头 \b 能够理解一个锚点的符号,此符号匹配的是单词的边界。这其中的word定义为连续的字母,数字和下划线。 准确的来讲,\b的位置是在\w和\W的交界处,固然还有字符串开始结束和\w之间。 \B 和\b对应,自己匹配空字符,可是其位置是在非"边界"状况下. 好比r'py\B'能够匹配'python',但不能匹配'py,','py.' 等等 \d 匹配数字 \D 匹配非数字 \s 未指定UNICODE和LOCALE标记时,等同于[ \t\n\r\f\v](注意\t以前是一个空格,表示也匹配空格) \S 与\s相反 \w 未指定UNICODE和LOCALE标记时,等同于[a-zA-Z0-9_] \W 和\w相反 \Z 只匹配字符串结尾 其余的一些python支持的转移符号也都有支持,如前面的'\t'
[]
尤为注意,这个字符集最终 只匹配一个字符(既不是空,也不是一个以上!),因此前面的一些量词限定符,在这里失去了原有的意义。
另外,'-'符号放在两个字符之间的时候,表示ASCII字符之间的全部字符,如[0-9],表示0到9.
而放在字符集开头或者结尾,或者被'\'转义时候,则只是表示特指'-'这个符号
最后,当在开头的地方使用'^',表示排除型字符组. python
(...) 普通捕获型括号,能够被\number引用。
(?aiLmsx) a re.A i re.I #忽略大小写 L re.L m re.M s re.S #点号匹配包括换行符 x re.X #能够多行写表达式 如: re_lx = re.compile(r'(?iS)\d+$') re_lx = re.compile(r'\d+',re.I|re.S) #这两个编译表达式等价
(?:......) #非捕获型括号,此括号不记录捕获内容,可节省空间
(?P<name>...) #此捕获型括号可使用name来调用,而没必要依赖数字。使用(?P=name)调用。
(?#...) #注释型括号,此括号彻底被忽略
(?=...) #positive lookahead assertion 若是后面是括号中的,则匹配成功
(?!...) #negative lookahead assertion 若是后面不是括号中的,则匹配成功
(?<=...) #positive lookbehind assertion 若是前面是括号中的,则匹配成功
(?<!...) #negative lookbehind assertion 若是前面不是括号中的,则匹配成功
#以上四种类型的断言,自己均不匹配内容,只是告知正则引擎是否开始匹配或者中止。
#另外在后两种后项断言中,必须为定长断言。
(?(id/name)yes-pattern|no-pattern)
#若有由id或者name指定的组存在的话,将会匹配yes-pattern,不然将会匹配no-pattern,一般状况下no-pattern能够省略。
如'??'会先匹配没有的状况,而后才是1个对象的状况。而{m,n}?则是优先匹配m个对象,而不是占多的n个对象。
正则表达式
python属于perl风格,属于传统型NFA引擎,与此相对的是POSIX NFA和DFA等引擎。因此大部分讨论都针对传统型NFA
express
NFA是基于表达式主导的引擎,同时,传统型NFA引擎会在找到第一个符合匹配的状况下当即中止:即获得匹配以后就中止引擎。
而POSIX NFA 中不会马上中止,会在全部可能匹配的结果中寻求最长结果。这也是有些bug在传统型NFA中不会出现,可是放到后者中,会暴露出来。
引伸一点,NFA学名为”非肯定型有穷自动机“,DFA学名为”肯定型有穷自动机“
这里的非肯定和肯定均是对被匹配的目标文本中的字符来讲的,在NFA中,每一个字符在一次匹配中即便被检测经过,也不能肯定他是否真正经过,由于NFA中会出现回溯!甚至不止一两次。图例见后面例子。而在DFA中,因为是目标文本主导,全部对象字符只检测一遍,到文本结束后,过就是过,不过就不过。这也就是”肯定“这个说法的缘由。 数组
做为对比说明,下面是目标文本不能匹配时,引擎走过的路径:
以下图,咱们看到此时POSIX NFA和传统型NFA的匹配路径是一致的。 缓存
以上的例子引起了一个匹配时的思考,不少时候咱们应该尽可能避免使用'.*' ,由于其老是能够匹配到最末或者行尾,浪费资源。
既然咱们只寻求引号之间的数据,每每能够借助排除型数组来完成工做。
此例中,使用'[^'']*'这个来代替'.*'的做用显而易见,咱们只匹配非引号的内容,那么遇到第一个引号便可退出*号控制权。 学习
'\w+:'
这个表达式在进行匹配时的流程是这样的,会优先去匹配全部的符合\w的字符,假如字符串的末尾没有':',即匹配没有找到冒号,此时触发回溯机制,他会迫使前面的\w+释放字符,而且在交还的字符中从新尝试与':'做比对。
可是问题出如今这里: \w是不包含冒号的,显然不管如何都不会匹配成功,但是依照回溯机制,引擎仍是得硬着头皮往前找,这就是对资源的浪费。
因此咱们就须要避免这种回溯,对此的方法就是将前面匹配到的内容固化,不令其存储备用状态!,那么引擎就会由于没有备用状态可用而只得结束匹配过程。大大减小回溯的次数! 测试
虽然python中不支持,但书中提供了利用前向断言来模拟固化过程。 优化
(?=(...))\1自己, 断言表达式中的结果是不会保存备用状态的,并且他也不匹配具体字符,可是经过巧妙的 添加一个捕获型括号来反向引用这个结果,就达到了固化分组的效果!对应上面的例子则是:
'(?=(\w+))\1:'
多选结构在传统型NFA中, 既不是匹配优先也不是忽略优先。而是按照顺序进行的。因此有以下的利用方式
spa
在处理过程当中,咱们老是习惯于使用星号等非硬性规定的量词(实际上是个很差的习惯),
这样的结果可能致使咱们使用的匹配表达式中没有必须匹配的字符,例子以下:
'[0-9]?[^*]*\d*' #只是举个例子,没有实际意义。上面的式子就是这种状况,在目标文本是“理想”时,可能出现不了什么问题,可是若是自己数据有问题。那么这个式子的匹配结果就彻底不可预知。
(\w+)*这种状况的表达式,在匹配长文本的时候会遇到什么问题呢,若是在文本匹配失败时(别忘了,若是失败,则说明已经回溯了 全部的可能),想象一下,*号退一个状态,里面的+号就包括其他的 全部状态,验证都失败后,回到外面,*号 退到倒数第二个备用状态,再进到括号内,+号又要回溯一边比上一轮差1的 备用状态数,当字符串很长时, 就会出现指数级的回溯总数。系统就会'卡死'。甚至当有匹配时,这个匹配藏在回溯总数的中间时,也是会形成卡死的状况。因此,使用NFA的引擎时,必需要注意这个问题!
咱们采用以下思路去避免这个问题:
占有优先量词(python中使用前向断言加反向引用模拟)
道理很简单,既然庞大的回溯数量都是被储存的备用状态致使的,那么咱们直接使引擎放弃这些状态。说究竟是摆脱(regex*)* 这种形式。
import re re_lx = re.compile(r'(?=(\w+))\1*\d')
在测试表达式的效率时,可借助如下代码比较所需时间。在两个可能的结果中择期优者。
import re import time re_lx1 = re.compile(r'your_re_1') re_lx2 = re.compile(r'your_re_2') starttime = time.time() repeat_time = 100 for i in range(repeat_time): s='test text'*10000 result = re_lx1.search(s) time1 = time.time()-starttime print(time1) starttime = time.time() for i in range(repeat_time): s='test text'*10000 result = re_lx2.search(s) time2 = time.time()-starttime print(time2)
下面这个例子假设出现匹配的内容在字符串对象的结尾,那么下面的第一个表达式是快于第二个表达式的,缘由在于前者有锚点的优点。
re_lx1 = re.compile(r'\d{5}$') re_lx2 = re.compile(r'\d{5}') #前者快,有锚点优化
继续,假设咱们要匹配一段字符串中的5位数字,会有以下两个表达式供选择:
通过分析,咱们发现\w是包含\d的,当使用匹配优先时,前面的\w会包含数字,之因此能匹配成功,或者肯定失败,是后面的\d迫使前面的量词交还一些字符。
知道这一点,咱们应该尽可能避免回溯,一个顺其天然的想法就是不让前面的匹配优先量词涉及到\d
re_lx1 = re.compile(r'^\w+(\d{5})') re_lx2 = re.compile(r'^[^\d]+\d{5}') #优于上面的表达式
整体来讲,在咱们没有时间去深刻研究模块代码的时候,只能经过尝试和反复修改来获得最终的复合预期的表达式。
避免从新编译(循环外建立对象) 使用非捕获型括号(节省捕获时间和回溯时状态的数量) 善用锚点符号 不滥用字符组 提取文本和锚点。将他们从可能的多选分支结构中提取出来,会提取速度。 最可能的匹配表达式放在多选分支前面
举个例子:
[^\\"]+(\\.[^\\"]+)* #匹配两个引号内的文本,可是不包括被转义的引号