Python文本处理(-)正则表达式操作-re模块 & regex模块

正则表达式(称为RE,或正则,或正则表达式模式)本质上是嵌入在Python中的一种微小的、高度专业化的编程语言。re模块提供来正则表达式匹配操作,用来对文本进行一些处理优化。匹配模式和被搜索的字符串既可以是Unicode字符串(str),也可以是8位字节串(bytes),不过两者不能混用。绝大多数正则表达式操作都提供了相应的函数,函数不需要先编译一个对象,但损失来一些优化参数。另外,第三方模块regex提供来与Python内置标准库re模块兼容的API接口,同时提供来额外的功能和更全面的Unicode支持。

正则表达式语法

正则表达式可以包含:普通字符特殊字符修饰符

普通字符就是平常使用的绝大多数字符’ABC’、’0’等;特殊字符是既可以表示普通含义,也可以影响其左右字符或表达式的含义,如’(‘,’|’等;修饰符是指用来修饰表达式的字符,其中修饰字符中的重复修饰符(*+?{m,n}, 等)不能直接套用以此避免与其它修饰符产生的多义性,但可以使用括号来应用内层重复嵌套。

常用的特殊字符和修饰符

字符 含义
. 默认模式匹配除了换行的任意字符;如果指定了DOTALL标签,则可以匹配换行字符
^ 默认模式匹配字符串开头;在MULTILINE模式下也会匹配换行后首个字符
$ 匹配字符串尾或者换行符的前一个字符;在MULTILINE模式下匹配换行符的前一个字符
* 对前面的正则表达式匹配0到任意次重复,如ab*匹配的组合有aababb或者其它a后面跟着n个b字符的组合
+ 对前面的正则表达式匹配1到任意次重复,如同样是ab组合,ab+匹配的组合有ab 、abb或者其它a后面跟着n个b字符的组合,但不会匹配a
? 对前面的表达式匹配0到1次重复,如ab?会匹配aab
*?+??? '*''+',和 '?' 修饰符都是重复修饰符,在字符串后面修饰符之后添加 ? 将使样式最小 方式进行匹配; 匹配的字符要尽量少,如 ab*?会按照*字符最小匹配次数0次进行匹配,也就是智能匹配a这一种情况。
{m} 对之前的正则表达式指定匹配m个重复,若少于m则不会匹配成功,如f{5}只能匹配fffff
{m,n} 对之前的正则表达式指定匹配m到n次匹配,在m到n之间尽可能多,如f{4,6}则可能匹配的值有fffffffffffffff
{m,n}? {m,n}模式下匹配尽量少的字符,如对于ffffff字符f{4,6}?只匹配前四个
\ 转义特殊字符;如果转义序列不被Python分析器识别,反斜杠和字符才能出现在字符串中,如果Python可以识别这个序列,那么反斜杠就要写两个
[] 用于表示一个字符集合;字符在[]可以单独列出,比如[ab]匹配a或者b;通过用-将两个数字连接表示字符范围,如[0-9]表示匹配数字0到数字9之间的数字;特殊字符在集合中会失去它本来的匹配含义,如[+*]只会匹配+或者*字符;字符类如 \w 或者 \S (如下定义) 在集合内可以接受;不在集合范围内的字符可以通过取反来进行匹配,如果集合首字符是^则所有不在集合内的字符会被匹配,如[^8]代表除了数字8之外将匹配所有的字符;若要在集合内匹配字符]要么放在集合的首位字符,要么加反斜杠\进行转义
` ` 连接任意两个正则表达式,如A\ B,将会匹配满足A表达式或满足B表达式的字符串
(...) 匹配括号内的任意正则表达式,并标识出组合的开始和结尾
(?...) 扩展类标记,'?'后面的第一个字符决定了这个构建采用什么样的语法。
(?aiLmsux) (?…)支持的扩展类型,( 'a''i''L''m''s''u''x' 中的一个或多个) 这个组合匹配一个空字符串;这些字符对正则表达式设置以下标记 re.A (只匹配ASCII字符), re.I(忽略大小写), re.L(语言依赖), re.M (多行模式), re.S (点dot匹配全部字符), re.U(Unicode匹配), and re.X (冗长模式)
(?:...) 匹配在括号内的任何正则表达式,但该分组所匹配的子字符串 不能 在执行匹配后被获取或是之后在模式中被引用。
(?imsx-imsx:...)  
(?P...) (命名组合)类似正则组合,但是匹配到的子串组在外部是通过定义的 name 来获取的。组合名必须是有效的Python标识符,并且每个组合名只能用一个正则表达式定义,只能定义一次。
(?P=name) 反向引用一个命名组合;它匹配前面那个叫 name 的命名组中匹配到的串同样的字串
(?#...) 注释;里面的内容会被忽略。
(?=...) 匹配 … 的内容,但是并不匹配除完全样式的内容。这个叫做 lookahead assertion。比如, Isaac (?=Asimov) 匹配 ‘Isaac ‘ 只有在后面是 ‘Asimov’ 的时候才会被匹配到。
(?!...) 匹配 … 不符合的情况。这个叫 negative lookahead assertion (前视取反)。比如说, Isaac (?!Asimov)只有后面 不 是 ‘Asimov’ 的时候才匹配 ‘Isaac ‘ 。
(?<=...) 匹配字符串的当前位置,它的前面匹配  的内容到当前位置。这叫:dfn:positive lookbehind assertion(正向后视断定)
(? 匹配当前位置之前不是  的样式。这个叫:dfn:negative lookbehind assertion (后视断定取非)。类似正向后视断定,包含的样式匹配必须是定长的。
`(?(id/name)yes-pattern no-pattern)` 如果给定的 id 或 name 存在,将会尝试匹配 yes-pattern ,否则就尝试匹配 no-pattern,no-pattern 可选,也可以被忽略。比如, (<)?(\w+@\w+(?:.\w+)+)(?(1)>

反斜杠符号\与普通字符组成的特殊序列

\和字符组成的特殊序列情况,如果普通字符不是ASCII数位或者ASCII字母,正则表达式将匹配第二个字符,此外构成的特殊序列有:

特殊序列 含义
\d 匹配任何十进制数字,等价于[0-9]
\D 匹配任何非数字字符,等价于[^0-9]
\s 匹配任何空白字符,等价于[ \t\n\r\f\v]
\S 匹配任何非空白字符,等价于[^ \t\n\r\f\v]
\w 匹配任何字母与数字字符,等价于[a-zA-Z0-9_]
\W 匹配任何非字母与数字字符,等价于[^a-zA-Z0-9_]
\A 只匹配字符串开始
\Z 只匹配字符串结束
\b 匹配空字符串,但只在单词开始或结尾的位置。通常 \b 定义为 \w 和 \W 字符之间,或者 \w 和字符串开始/结尾的边界
\B 匹配空字符串,但  能在词的开头或者结尾

正则表达式使用

re模块提供了API接口re.compile()来将正则编译为对象,然后用来进行匹配。

示例代码:

>>> import re
>>> res = re.compile('ab+')
>>> res
re.compile('ab+')

其中re.compile()也可以接收可选的flags参数,具体的参数值后面单独模块介绍

>>> res = re.compile('ab+', re.IGNORECASE)

re模块是Python附带的C扩展模块,正则被处理称为了字符串,并没有创建用于表达式正则的特殊语法。

反斜杠特别注意问题&解决方法

\可以用来表示特殊形式或不调用特殊含义,又因为Python将正则表达式处理成来字符串来处理, 而反斜杠在普通的 Python 字符串里也有相同的作用,所以就产生了冲突。比如说要匹配一个字母上的反斜杠,其正则表达式要写成\\\\,因为正则表达式力匹配一个反斜杠必须是\\,而每个反斜杠在普通Python字符中也要写成\\,所以造成如果正则表达式匹配一个反斜杠,必须要写成\\\\的问题。

问题的解决办法是:对于正则表达式样式使用Python的原始字符串表示,使用字符前面带r字符表示法用Python代码编写正则表达式。这么做的原因是在带有 'r' 前缀的字符串字面值中,反斜杠不必做任何特殊处理。 因此 r"\n" 表示包含 '\' 和 'n' 两个字符的字符串,而 "\n"则表示只包含一个换行符的字符串。

re模块内容编译标志

re模块定义了一些常量作为内容编译标志,编译标志允许修改正则表达式的工作方式,例如前面re.compile()函数中使用的flag参数就是re模块的内容编译标志。

re模块内容编译标志有

编译标志 含义
re.A / re.ASCII 让 \w\W\b\B\d\D\s 和 \S 只匹配ASCII,而不是Unicode。这只对Unicode样式有效,会被byte样式忽略。相当于前面语法中的内联标志 (?a);为了保持向后兼容, re.U 标记依然存在(还有他的同义  re.UNICODE 和嵌入形式 (?u) ) , 但是这些在 Python 3 是冗余的,因为默认字符串已经是Unicode了(并且Unicode匹配不允许byte出现)
re.DEBUG 显示编译时的debug信息,没有内联标记
re.I /re.IGNORECASE 进行忽略大小写匹配;表达式如 [A-Z] 也会匹配小写字符。Unicode匹配(比如 Ü 匹配 ü)同样有用,除非设置了 re.ASCII 标记来禁用非ASCII匹配。当前语言区域不会改变这个标记,除非设置了 re.LOCALE 标记,相当于内联标记 (?i) 。
re.L / re.LOCALE 由当前语言区域决定 \w\W\b\B 和大小写敏感匹配。这个标记只能对byte样式有效。这个标记不推荐使用,因为语言区域机制很不可靠,它一次只能处理一个 语句,而且只对8位字节有效。Unicode匹配在Python 3 里默认启用,并可以处理不同语言。 这个对应内联标记 (?L);Python3.6改版后re.LOCALE只能用于byte样式,并且不能与re.ASCII一起用
re.M / re.MULTILINE 设置以后,样式字符 ‘^’ 匹配字符串的开始,和每一行的开始(换行符后面紧跟的符号);样式字符 ‘\$’ 匹配字符串尾,和每一行的结尾(换行符前面那个符号)。默认情况下,’^’ 匹配字符串头,’\$’ 匹配字符串尾。对应内联标记 (?m) 。
re.S / re.DOTALL 让 ‘.’ 特殊字符匹配任何字符,包括换行符;如果没有这个标记,’.’ 就匹配 除了 换行符的其他任意字符。对应内联标记(?s) 。
re.X / re.VERBOSE 这个标记使得正则表达式具备更友好的可读性。通过分段和添加注释。空白符号会被忽略,除非在一个字符集合当中或者由反斜杠转义,或者在 *?(?: or (?P<…> 分组之内。当一个行内有 # 不在字符集和转义序列,那么它之后的所有字符都是注释。对应内联标记 (?x) 。

以下两种表达式等价,匹配一个十进制数字

>>> a = re.compile(r"""\d +  # the integral part
                   \.    # the decimal point
                   \d *  # some fractional digits""", re.X)
>>> b = re.compile(r"\d+\.\d*")

字符比较

利用re.compile()函数将正则表达式编译为字符串后,若要将被匹配的字符串与其比较,则需要调用其内置的函数,但re模块提供了相同功能的函数,其效果等同。

如:

prog = re.compile(pattern)
result = prog.match(string)

等价于:

result = re.match(pattern, string)

虽然以上两种方式等同,但模块级的函数会被缓存,所以不用考虑编译问题;如果需要多次使用同一个正则表达式,保存这个正则对象以便复用,可以让程序更加高效。其它函数也与上面的情况相同。

re模块的字符比较函数有:

函数 含义
re.match(patternstringflags=0) 如果 string 开始的0个或者多个字符匹配到了正则表达式样式,就返回一个相应的 匹配对象(后面模块介绍)。 如果不匹配,就返回 None ;值的注意的是该比较函数跟零长度匹配不同
re.search(patternstringflags=0) 扫描整个 字符串 找到匹配样式的第一个位置,并返回一个相应的 匹配对象。如果不匹配,就返回一个 None ; 同样值的注意的是该函数与找到一个零长度匹配不同。
re.fullmatch(patternstringflags=0) 确定正则是否从字符串的开头到结尾是否完全匹配。如果整个 string 匹配到正则表达式样式,就返回一个相应的 匹配对象 。 否则就返回一个 None。Python3.4之后版本功能
re.findall(patternstringflags=0) 对 string 返回一个与pattern表达式式规则匹配的不重复 列表, string 从左到右进行扫描,按找到的匹配顺序返回。如果样式里存在一到多个组,就返回一个组合列表;就是一个元组的列表,空匹配也会包含在结果里。
re.finditer(patternstringflags=0) 找到正则匹配的所有子字符串,并将它们返回为一个 迭代器类型,string 从左到右进行扫描,按找到的匹配顺序返回,空匹配也会包含在结果里。

字符串修改

re模块同样提供了对字符串截取、替换等字符串操作。提供的函数有:

函数 释义
re.split(patternstringmaxsplit=0flags=0) 用 pattern 分开  string 。 如果在 pattern 中捕获到括号,那么所有的组里的文字也会包含在列表里。如果 maxsplit 非零, 最多进行 maxsplit 次分隔, 剩下的字符全部返回到列表的最后一个元素
re.sub(patternreplstringcount=0flags=0) 返回通过替换 replacement 替换 string 中正则的最左边非重叠出现而获得的字符串。 如果未找到模式,则 string 将保持不变。可选参数 count 是要替换的模式最大的出现次数;count 必须是非负整数。 默认值 0 表示替换所有。
re.subn(patternreplstringcount=0flags=0) 行为与 sub()相同,但是返回一个元组 (字符串, 替换次数).
re.escape(pattern) 匹配除ASCII、数字和’_’以外的所有字符。如果要匹配可能包含正则表达式元字符的任意文本字符串,该函数比较适用。
re.purge() 清除正则表达式缓存
re.error(msgpattern=Nonepos=None) 当传递到函数的字符串不是一个有效正则表达式的时候(比如,包含一个不匹配的括号)或者其他错误在编译时或匹配时产生。如果字符串不包含样式匹配,是不会被视为错误的。错误实例有以下附加属性,msg:未格式化的错误消息; pattern:正则表达式样式;pos:编译失败的pattern位置索引; lineno:对应pos的行号;colno:对应pos的列号
>>> import re
>>> resplit = re.compile(r'\W+')
>>> resplit.split('This is a test, short and sweet, of split().')
['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', '']
#上面的方式与下面调用re模块函数等价
>>> re.split(r'\W+', 'This is a test, short and sweet, of split().')
['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', '']
>>> re.split(r'\W+', 'This is a test, short and sweet, of split().', 1)
['This', 'is a test, short and sweet, of split().']
>>> re.split(r'\W+','This is a test,short and sweet, of spilt().', 3)
['This', 'is', 'a', 'test,short and sweet, of spilt().']
#如果分隔符里有捕获组合,并且匹配到字符串的开始,结果将返回一个以空字符开头的列表
>>> re.split(r'\W+', '...This is a test, short and sweet, of spilt()', 3)
['', 'This', 'is', 'a test, short and sweet, of spilt()']

#sub函数
>>> resubs = re.compile('(blue|white|red)')
>>> resubs.sub('color', 'blue T-shirt and red T-shirt')
'color T-shirt and color T-shirt'
>>> resubs.sub('color', 'blue T-shirt and red T-shirt', count=1)
'color T-shirt and red T-shirt'
#re模块等价函数
>>> re.sub('(blue|white|red)', 'color', 'blue T-shirt and red T-shirt', 2)
'color T-shirt and color T-shirt'

#subn函数
>>> p = re.compile('(blue|white|red)')
>>> p.subn('color', 'blue socks and red shoes')
('color socks and color shoes', 2)
>>> p.subn('color', 'no colors at all')
('no colors at all', 0)
>>> re.subn('(blue|white|red)', 'color', 'blue socks and red shoes')
('color socks and color shoes', 2)
>>> re.subn('(blue|white|red)','color','no colors at all')
('no colors at all', 0)

# sub函数只有当空匹配项与前一个匹配项不相邻时,才会替换
>>> re.sub('x*', '-', 'abxd')
'-a-b-d-'

匹配对象

match()和search()两个函数会返回一个匹配对象,匹配对象总是有个Bool值,如果能够匹配则返回结果有值,如果不匹配,则匹配对象不存在,可以用以下方式进行检验

match = re.search(pattern, string)
if match:
    print (match)
#实例如下:
>>> match = re.match('ab*', 'abb')
>>> if match:
       print (match)

<_sre.SRE_Match object; span=(0, 3), match='abb'>
>>>

匹配对象具有一些方法和属性可以进行后续操作:

函数 释义
match.expand(template) 返回通过对模板字符串模板执行反斜杠替换获得的字符串
math.group([group1,….]) 返回一个或者多个匹配的子组。如果只有一个参数,结果就是一个字符串,如果有多个参数,结果就是一个元组(每个参数对应一个项),如果没有参数,组1默认到0(整个匹配都被返回)。 如果一个组N 参数值为 0,相应的返回值就是整个匹配字符串;如果它是一个范围 [1..99],结果就是相应的括号组字符串。如果一个组号是负数,或者大于样式中定义的组数,一个 IndexError 索引错误就 会产生。如果一个组包含在样式的一部分,并被匹配多次,就返回最后一个匹配。
math.getitem(g) 等价于 m.group(g)
match.groups(default=None) 回一个元组,包含所有匹配的子组,在样式中出现的从1到任意多的组合。 default 参数用于不参与匹配的情况,默认为 None
match.groupdic(default=None) 返回一个字典,包含了所有的 命名 子组。key就是组名。 default 参数用于不参与匹配的组合;默认为 None
match.start(g) 返回 group 匹配到的字串的开始标号。group默认为0(意思是整个匹配的子串)。如果 group 存在,但未产生匹配,就返回 -1
match.end(g) 返回 group 匹配到的字串的结束标号。group默认为0(意思是整个匹配的子串)。如果 group 存在,但未产生匹配,就返回 -1
match.span(g) 对于一个匹配 m , 返回一个二元组 (m.start(group), m.end(group)) 。 注意如果 group 没有在这个匹配中,就返回 (-1, -1) 。group 默认为0,就是整个匹配
match.pos search()或match()函数进行匹配时设置的pos值
match.endpos search()或match()函数进行匹配时设置的endpos值
match.lastindex 捕获组的最后一个匹配的整数索引值,或者 None 如果没有匹配产生的话。比如,对于字符串 'ab',表达式 (a)b((a)(b)), 和 ((ab)) 将得到 lastindex == 1 , 而 (a)(b) 会得到 lastindex == 2
match.lastgroup 最后一个匹配的命名组名字,或者 None 如果没有产生匹配的话
mathc.re match()或search()方法生成匹配实例的正则表达式对象
match.string 传递给match()或search()的字符串

match()与search() 对比

re.match()函数和re.search()函数是Python提供的两种不同操作,re.match()检查字符串开头,而re.search()检查字符串中的任意位置

如下情况,match不匹配,但search进行匹配

>>> re.match('super', 'insuper')
>>> re.search('super', 'insuper')
<_sre.SRE_Match object; span=(2, 7), match='super'>

可以用’^’作为限制让search()函数从首位开始匹配

>>> re.search('super', 'insuper')
<_sre.SRE_Match object; span=(2, 7), match='super'>
>>> re.search('^super', 'insuper')

MULTILINE多行模式中函数match()只匹配字符串的开始,但使用search()和以 '^' 开始的正则表达式会匹配每行的开始

>>> re.match('^X', 'A\nB\nX', re.MULTILINE)
>>> re.search('^X', 'A\nB\nX', re.MULTILINE)
<_sre.SRE_Match object; span=(4, 5), match='X'>

regex模块

regex模块实现是在re模块的基础上,但提供了额外的功能。re模块在Python3.7中对零长度匹配做了修改,regex模块在Python3.7中也会遵循re模块的设定。

Unicode

regex模块支持Unicode11.0,并且中Unicode匹配不区分大小写。regex模块支持简单和完整的大小写折叠,用于Unicode中不区分大小写的匹配。匹配模式可以使用 FULLCASE 、 F标志,或(?f),但这些标志会影响IGNORECASE标志的工作,FULLCASE标志本身不会启用不区分大小写的匹配。在V.0版本中,该标志默认是关闭的;在V1.0中,该标志默认开启。

###嵌套集合和集合操作

在最初始版本中因为regex模块中对为转义的 [ 字符设定含义不同,所以regex模块不能支持re模块中由[]定义的嵌套集合。举例来说, [[a-z]--[aeiou]] 在V.0中被解读为:

1.包含“[”和字母“A”到“Z”的集合

2.字面量-

3.包含字母 “a”, “e”, “i”, “o”, “u”

4.字母量]

但在V.1中解读为:

1.包含字母’a’到’z’的集合

2.但是不包括字母“a”, “e”, “i”, “o”, “u”

这是英文V.0只支持简单集合,直到V.1才支持嵌套集合和集合操作。

标志

regex模块中有两大类型标志:范围标志和全局标志。范围标志应用于局部正则表达式,可设置为True或False;全局标志被应用于全局变量,但只可设置为True。

范围标志有: FULLCASEIGNORECASEMULTILINE、 DOTALL、 VERBOSEWORD

全局标志有: ASCIIBESTMATCH、 ENHANCEMATCHLOCALEPOSIXREVERSE、 UNICODE、 VERSION0VERSION1

如果 ASCIILOCALEUNICODE 三个标志都没有被明确定义,正则表达式匹配的是Unicode字符的情况下,默认开启 UNICODE 标志;正则表达式匹配的是bytes类型,默认开启 ASCII标志。

ENHANCEMATCH开始的是模糊匹配模式,会尝试改进找到下一个匹配项。

BESTMATCH标志使模糊匹配尝试找到最佳结果。

多线程

regex模块在匹配内置(不可变)字符串类型对象时会及时释放GIL,这样其它的Python线程可以并发运行。此外可以通过设置关键字参数concurrent=True调用匹配方法,可以强制释放GIL,但这种情况的使用只能在保证字符串在匹配过程中不会发生改变的情况下才可以使用。

regex正则表达式对象函数

regex模块提供的方法和属性有:

函数 释义
regex.match(string, pos, endpos) 如果 string 的 开始位置 能够找到这个正则样式的任意个匹配,就返回一个相应的匹配对象,否则就返回 None;可选的第二个参数 pos 给出了字符串中开始搜索的位置索引;默认为 0;可选参数 endpos 限定了字符串搜索的结束;它假定字符串长度到 endpos , 所以只有从 pos 到 endpos -1 的字符会被匹配。如果 endpos 小于 pos,就不会有匹配产生
regex.search(string, pos, endpos) 扫描整个 string 寻找第一个匹配的位置, 并返回一个相应的匹配对象,否则就返回 None;可以接收可选参数 pos 和 endpos 来确定操作的开始字符和结束字符来限制范围
regex.fullmatch(string, pos, endpos) 如果整个 string 匹配这个正则表达式,就返回一个相应的匹配对象,否则就返回 None;可以接收可选参数 pos 和 endpos 来确定操作的开始字符和结束字符来限制范围
regex.split(string, maxsplit=0) 等价于re.spilt()函数
regex.findall(string, pos, endpos) 类似函数 findall() ,可以接收可选参数 pos和 endpos 来确定操作的开始字符和结束字符来限制范围
regex.finditer(string, pos, endpos) 类似函数 finiter() ,可以接收可选参数 pos 和 endpos 来确定操作的开始字符和结束字符来限制范围
regex.sub(repl, string, count=0) 等价于 re模块中的re.sub()函数
regex.subn(*repl, strring, count=0) 等价于 re模块中的re.subn()函数
regex.flags 正则匹配标记
regex.groups 捕获组合的数量。
regex.groupindex 映射由 (?P) 定义的命名符号组合和数字组合的字典。如果没有符号组,那字典就是空的
regex.pattern 编译的RE对象的字符串模式

代码示例:

>>> import regex
>>>#设定的匹配规则是:匹配以十进制数字开头对十进制数字匹配超过1次 或者满足匹配字母和数字超过1次;其中 ?= 代表lookahead assertion
>>> regex.match(r'(?(?=\d)\d+|\w+)', '123abc')

>>> regex.match(r'(?(?=\d)\d+|\w+)', 'abc123')

>>>#对于上面的匹配规则可以看出|匹配符 若满足第一个正则表达式则不判断第二个表达式,例如第一个例子中给出的字符是'123abc'然后前面的123 满足第一条匹配规则,所以看到匹配对象返回的span是(0,3);而对于第二个例子中给出的字符'abc123'并不满足第一条匹配十进制数字说以进行第二项表达式的匹配,发现与第二条表达式匹配,匹配结果是'abc123',所以返回的span是(0,6)
>>>>>> print (regex.match(r'(?(?=\d)\d+\b|\w+)', '123abc'))
None
>>> print (regex.match(r'(?:(?=\d)\d+\b|\w+)', '123abc'))

>>#在上面的操作中修改了正则表达式,加入\b代表匹配的数字开始和结束必须是空字符;第一条语句在'123abc'不满足条件的时候没有进行第二条正则表达式的匹配;第二条语句因为?:代表后面的语句都要匹配一遍,所以在第一条正则表达式不满足条件的时候去匹配了第二条正则表达式


使用POSIX返回最左边的最长匹配,可以使用POSIX标志(?P)

>>>#正常拼写规则
>>> regex.search(r'Mr|Mrs', 'Mrs')

>>> regex.search(r'one(self)?(selfsufficient)?', 'oneselfsufficient')

>>>#使用POSIX标志之后
>>> regex.search(r'(?p)Mr|Mrs', 'Mrs')

>>> regex.search(r'(?p)one(self)?(selfsufficient)?', 'oneselfsufficient')

不过这样做的缺点是:耗时更长,因为在找到匹配项之后不会立即返回,而是继续查找看是否有更长的匹配项


使用?(DEFINE)在没有分组名为’DEDINE’的情况下,可以使用其在分组

>>> regex.search(r'(?(DEFINE)(?P\d+)(?P\w+))(?&quant) (?&item)', '5 elephants')

>>> regex.search(r'(?P\d+)(?P\w+)(?&quant) (?&item)', '5 elephants')
>>> 

正则表达式中添加(*PRUNE) (*SKIP) and (*FAIL) 情况如下:

(*PRUNE)能将回溯信息丢弃到该点,在原子组 或 lookaround中使用时不会影响封闭模式。

(*SKIP) 与 (*PRUNE)类似,但是会设置文本中下次尝试匹配的起始位置,同样在原子组 或 lookaround中使用时不会影响封闭模式。

(*FAIL)会导致立即回溯,(*F)是其缩写。


使用\K则会整个在K位置出现之后的匹配部分,在K位置之前的将被舍弃,但是不会影响捕获组返回的内容。

>>> m = regex.search(r'(\w\w\K\w\w\w)', 'abcdef')
>>> print (m)

>>> m[0]
'cde'
>>> m[1]
'abcde'
>>>
>>> m = regex.search(r'(?r)(\w\w\K\w\w\w\w)', 'abcdef')
>>> m[0]
'ab'
>>> m[1]
'abcdef'

可以使用订阅获取重复捕获组的捕获为expandf、subf、subfn添加捕获订阅

>>> m = regex.match(r"(\w)+", "abc")
>>> m.expandf("{1}")
'c'
>>> m.expandf("{1[0]} {1[1]} {1[2]}")
'a b c'
>>> m.expandf("{1[-1]} {1[-2]} {1[-3]}")
'c b a'
>>>
>>> m = regex.match(r"(?P\w)+", "abc")
>>> m.expandf("{letter}")
'c'
>>> m.expandf("{letter[0]} {letter[1]} {letter[2]}")
'a b c'
>>> m.expandf("{letter[-1]} {letter[-2]} {letter[-3]}")
'c b a'

部分匹配是指匹配到字符串结尾的匹配项,但是前提是字符串已提前分割,只是想知道如果字符未被截断,会不会是完全匹配。部分匹配支持match、 search、 fullmatch、 findite函数,函数参数为partial。 通过设置参数partial可以决定是否开启部分匹配。

举例:限制用户输入4个数字,并且在输入的时候逐字检查

>>> import regex
>>> pattern = regex.compile(r'\d{4}')
>>> #最初没有值输入
>>> print (pattern.fullmatch(' ', partial=True))
None
>>> #可以是空字符,此时是部分匹配
>>> #用户输入字母,不符合匹配规则
>>> print (pattern.fullmatch('a', partial=True))
None
>>> #用户输入数字,符合匹配规则,此时也是部分匹配
>>> print (pattern.fullmatch('2', partial=True))

>>> #用户继续输入数字
>>> print (pattern.fullmatch('234', partial=True))

>>> #以上输入的内容仍然符合匹配规则,但依旧是部分匹配
>>> #用户继续输入
>>> print (pattern.fullmatch('2345', partial=True))

>>> #此时的匹配是完全匹配不再是部分匹配
>>> pattern.fullmatch('2345', partial=True).partial
False

*操作符在sub()函数中不能正确的工作

>>> regex.sub('(?V0).*', 'x', 'test')
'x'
>>> regex.sub('(?V1).*', 'x', 'test')
'xx'
>>> regex.sub('(?V0).*', '|', 'test')
'|'
>>> regex.sub('(?V0).*?', '|', 'test')
'|t|e|s|t|'
>>> regex.sub('(?V1).*?', '|', 'test')
'|||||||||'

capturesdictgroupdictcaptures的结合,groupdict返回分组信息和统计,captures返回分组特征的具体信息,而capturesdict返回分组信息列表,列表中包含每个特征信息 。

>>> m = regex.match(r"(?:(?P\w+) (?P\d+)\n)+", 'one 1\ntwo 2\nthree 3\n')
>>> m.groupdict()
{'word': 'three', 'digits': '3'}
>>> m.captures('word')
['one', 'two', 'three']
>>> m.captures('digits')
['1', '2', '3']
>>> m.capturesdict()
{'word': ['one', 'two', 'three'], 'digits': ['1', '2', '3']}

支持拷贝分组名称

>>> #两个分组特征,第二个特征会覆盖第一个特征
>>> m = regex.match(r"(?P\w+)? or (?P\w+)?", 'first or third')
>>> m.group('item')
'third'
>>> m.captures('item')
['first', 'third']
>>> #只有第二个特征分组
>>> m = regex.match(r"(?P\w+)? or (?P\w+)?", " or second")
>>> m.group('item')
'second'
>>> m.captures('item')
['second']
>>> #两个分组特征,第二个特征会覆盖第一个特征
>>> m = regex.match(r'(?P\w*) or (?P\w+)', ' or second')
>>> m.group('item')
'second'
>>> m.captures('item')
['', 'second']

匹配对象包含对搜索字符串的引用,通过调用其属性string可以查看。通过detach_string可以分离原字符串,使字符串可用于垃圾回收,如果字符串非常大,可以减少内存使用

>>> m = regex.search(r'\w+', 'Hi MuYing')
>>> print (m.group())
Hi
>>> print (m.string)
Hi MuYing
>>> m.detach_string()
>>> print (m.group())
Hi
>>> print (m.string)
None

使用?R 或则’?0’可以进行递归匹配,?1?2可以抓取对应的捕获组

>>> m = regex.search(r'(\w)(?:(?R)|(\w?))\1', 'kayak')
>>> m.group(0, 1, 2)
('kayak', 'k', None)
>>> regex.match(r"(Tarzan|Jane) loves (?1)", "Tarzan loves Jane").groups()
('Tarzan',)
>>> regex.match(r"(MuYing|drawing) loves (?1)", "MuYing loves drawing").groups()
('MuYing',)

regex通常尝试精确匹配,但有时需要进行近似匹配或“模糊”匹配,因为搜索的文本可能包含插入、删除或替换字符形式的错误。模糊正则表达式指定允许哪些类型的错误,以及可选地指定每种类型的最小值和最大值,或者只指定允许的最大值。(不能只指定最小值)

指定的类型错误有:

(1) i 表示插入

(2)d表示删除

(3)s表示替代

(4)e表示任何错误类型

在近似匹配中,如果指定了某种错误类型,就不能再指定任何错误类型。默认情况下,模糊匹配搜索满足给定约束的第一个匹配。EnhanceMatch标志将使其尝试改进已找到匹配的匹配(即减少错误数)。BestMatch标志将使其搜索最佳匹配。并且匹配对象存在一个属性可以查看指定的删除、插入、替代错误数量,该属性是fuzzy_counts。匹配对象还有一个属性fuzzy_changes,它给出了替换、插入和删除位置的元组。

>>> import regex
>>> regex.fullmatch(r'(?:cat|cat){e<=1}', 'cat').fuzzy_counts
(0, 0, 0)
>>> # 0个substitutions, 0个insertions, 1个deletion
>>> regex.fullmatch(r'(?:cats | cat){e<=1}', 'cat').fuzzy_counts
(0, 0, 1)
>>> regex.search('(fuu){i<=2,d<=2,e<=5}', 'anaconda foo bar').fuzzy_changes
([], [7, 8], [9, 10])

\L可以用来设置选项列表

其中一种设置形式如下:

>>> m = regex.compile(r'first|second|third|fourth|fifth')

但是有些情况下列表可能过长,这个时候可以用\L,并且可以用named_lists属性查看

>>> m = regex.compile(r'first|second|third|fourth|fifth')
>>> option_set = ['first', 'second', 'third', 'fourth', 'fifth']
>>> m = regex.compile(r'\L', options=option_set)
>>> print (m.named_lists)
{'options': frozenset({'second', 'fifth', 'first', 'third', 'fourth'})}

regex模块支持集合操作

(1)|| 异或运算 例如:a||b 取值a或者b

(2)~~ 差分运算 例如:x~~y 取值x或者y但是不能都包含

(3)&& 结合运算 例如: x&&y 取值xy

(4)-- 差异运算 例如: x-y 取值x不能是y

举例: 

  • [\p{L}--QW] # 包含所有的字母 除了字母 ‘Q’ 和‘W’
  • [[a-z] — [qw]] #字母a…z,但是不包含字母q和w
  • [\p{N}--[0-9]] # 所有的数字 除了 ‘0’ .. ‘9’
  • [\p{ASCII}&&\p{Letter}] #所有的ASCII和字符集

部分模式控制正则表达式以及占有量词

(?>…) #如果部分匹配模式失败,则整个匹配判定为失败

(?:...)?+ 、(?:...)*+(?:...)++(?:...){min,max}+ 以上匹配模式最大匹配max次,同样如果部分匹配模式失败,则整个匹配判定为失败,例如 (?:...)++ 正则匹配表达式与 (?>(?:...)+)有着相同的含义。


regex模块支持POSIX类字符,被转化为\p{...}形式。但也有不一样的情况,例如alnum、 digitpunctxdigit,这些定义不同于普通的unicode字符。

[[:alnum:]]等价于\p{posix_alnum}

[[:digit:]]等价于\p{posix_digit}

[[:punct:]]等价于\p{posix_digit}

[[:xdigit:]]等价于\p{posix_xdigit}


搜索标志\G,在每个搜索开始/继续的位置匹配,可以用于连续匹配,也可以用于可变长度的lookbehinds,以限制lookbehind向后进行的距离

>>> regex.findall(r'\w{2}', 'abcd ef')
['ab', 'cd', 'ef']
>>> regex.findall(r'\G\w{2}', 'abcd ef')
['ab', 'cd']

添加了搜索标志\G后的匹配步骤如下:

第一步:搜索匹配从第0个位置开始,匹配了两个字母’ab’

第二步: 搜索匹配从第2个位置开始,匹配了两个字母’cd’

第三步:搜索匹配继续从第四个位置开始,但是没有匹配到任何字母

第四步: 搜索标志停止搜索开始位置的前进,因此没有更多的结果了

regex模块同时还支持反向搜索,但反向搜索的结果不一定是正向结果的反向结果

>>> regex.findall(r'.', 'abc')
['a', 'b', 'c']
>>> regex.findall(r'(?r).', 'abc')
['c', 'b', 'a']
>>> regex.findall(r'..', 'abcde')
['ab', 'cd']
>>> regex.findall(r'(?r)..', 'abcde')
['de', 'bc']

在Python3版本中,regex的匹配方法和函数支持超时,超时适用于regex的所有操作

>>> from time import sleep
>>> def fast_replace(m):
    return 'X'

>>> def slow_replace(m):
    sleep(0.5)
    return 'X'

>>> regex.sub(r'[a-z]', fast_replace, 'abcde', timeout=2)
'XXXXX'
>>> regex.sub(r'[a-z]', slow_replace, 'abcde', timeout=2)
'XXXXX'

其它应用示例

>>> print (regex.fullmatch(r'abc',  'abc').span())
(0, 3)
>>> print (regex.fullmatch(r'abc',  'abcd'))
None
>>> print (regex.fullmatch(r'abc', 'abcd', endpos=3).span())
(0, 3)
>>> print (regex.fullmatch(r'abc', 'xabcx', pos=1, endpos=4).span())
(1, 4)
>>> regex.match(r'a.*?', 'abcd').group(0)
'a'
>>> regex.fullmatch(r'a.*?', 'abcd').group(0)
'abcd'
>>> regex.subf(r'(\w+) (\w+)', '{0} => {2} {1}',  'foo bar')
'foo bar => bar foo'
>>> regex.subf(r'(?P\w+) (?P\w+)', '{word2} {word1}', 'foo bar' )
'bar foo'
>>> m = regex.match(r'(\w+) (\w)', 'foo bar')
>>> m.expandf('{0} => {2} {1}')
'foo b => b foo'
>>>
>>>#接下来是escape()相关操作
>>>#如果special_only为真,则只限制转义特殊regex字符
>>> regex.escape('foo!?', special_only=False)
'foo\\!\\?'
>>> regex.escape('foo!?', special_only=True)
'foo!\\?'
>>>#如果literal_spaces属性设置为真,则不转义空字符
>>> regex.escape('foo bar!?', literal_spaces=False)
'foo\\ bar!\\?'
>>> regex.escape('foo bar!?', literal_spaces=True)
'foo bar!\\?'
>>>
>>>#接下来是相关start()、end()、span()等相关操作
>>>>>> m = regex.search(r'(\w{3})+', '12345678')
>>> m.group(1)
'456'
>>> m.captures(1)
['123', '456']
>>> m.start(1)
3
>>> m.starts(1)
[0, 3]
>>> m.end(1)
6
>>> m.ends(1)
[3, 6]
>>> m.span(1)
(3, 6)
>>> m.spans(1)
[(0, 3), (3, 6)]
>>>
>>>

你可能感兴趣的:(Python基础夯实,python,正则表达式,re,regex)