提示: 因为该站对MARKDOWN的表格支持的不是很好,因此本文中的表格均以图片的形式提供,你们若是看着比较模糊,能够放大来看或下载图片在本地查看。html
正则表达式(Regluar Expressions)又称规则表达式,在代码中常简写为REs,regexes或regexp(regex patterns)。它本质上是一个小巧的、高度专用的编程语言。 经过正则表达式能够对指定的文本实现
匹配测试、内容查找、内容替换、字符串分割 等功能。正则表达式的语法不是本节要讲的内容(关于正则表达式的详细介绍请参考另外一篇博文《正则表达式总结》),本节主要介绍的是Python中是如何使用re模块来完成正则表达式的相关操做的。python
Python中的re模块提供了一个正则表达式引擎接口,它容许咱们将正则表达式编译成模式对象,而后经过这些模式对象执行模式匹配搜索和字符串分割、子串替换等操做。re模块为这些操做分别提供了模块级别的函数以及相关类的封装。正则表达式
Python中的re模块中最重要的两个类:编程
类 | 描述 |
---|---|
Regular Expression Objects | 正则表达式对象,用于执行正则表达式相关操做的实体 |
Match Objects | 正则表达式匹配对象,用于存放正则表达式匹配的结果并提供用于获取相关匹配结果的方法 |
经过re模块的compile()函数编译获得的正则表达式对象(下面用regex表示)支持以下方法:socket
说明: 若是指定了pos和endpos参数,就至关于在进行正则处理以前先对字符串作切片操做 string[pos, endpos],如rx.search(string, 5, 50)就等价于rx.search(string[5:50]),也等价于rx.search(string[:50], 5);若是endpos比pos的值小,则不会找到任何匹配。编程语言
调用正则表达式对象的regex.match()、regex.fullmatch()和regex.search()获得的结果就是一个匹配对象,匹配对象支持如下方法和属性:函数
re模块提供了如下几个模块级别的函数工具
将pattern参数只能是字符串;测试
说明: 经过对比会发现,上面这些re模块级别的函数除了re.compile、re.purge和re.escape这几个函数外,其它函数名都与正则表达式对象支持的方法同名。实际上re模块的这些函数都是对正则表达式对象相应方法的封装而已,功能是相同的。只是少了对pos和endpos参数的支持,可是咱们能够手动经过字符串切片的方式来达到相应的需求。我的认为,咱们应该尽量的使用模块级别的函数,这样能够加强代码的兼容性。ui
由上面的描述可知,flags参数在上面这些模块函数中是要个可选参数,re模块中预约了该参数可取的值:
说明: 这些flag能够单独使用,也能够经过逻辑或操做符'|'进行拼接来联合使用。
咱们有必要对re模块中所包含的类及其工做流程进行一下简单的、总体性的说明,这讲有利于咱们对下面内容的理解。
总结: 关于正则表达式的语法和编写示例请参考《正则表达式总结》)。根据上面的描述可知,将一个表示正则表达式的Python字符串编译成一个正则表达式对象是使用正则表达式完成相应功能的首要步骤,re模块中用于完成正则表达式编译功能的函数为re.compile()。
在上面的内容中,咱们已经列出了re模块所提供的类,以及这些类的对象所支持的函数和属性,还有re模块所提供的模块级别的函数。这里咱们要讨论的是怎样合理的使用这些方法和函数,换句话说就是在什么状况下使用这些方法和函数,以及如何使用的问题。咱们以前说过,正则表达式主要能够用来提供如下几个功能:
下面咱们就分别经过对这几个功能的实例实现对上面这些函数和方法以及参数和属性的使用作下说明和具体的解释。
匹配测试,意思是经过特定的正则表达式对一个指定的字符串进行匹配来判断该字符串是否符合这个正则表达式所要求的格式。常见的使用场景举例:
经过re模块执行匹配测试时可使用的函数或方法是:
前面提到过,match()函数或方法只是匹配字符串的开始位置,而fullmatch匹配的是整个字符串;fullmatch()函数或方法就至关于给match()函数或方法的pattern或string参数加上行首边界元字符'^'和行尾边界元字符'$',下面来看个例子:
import re # 定义一个函数来对匹配结果进行展现 def display_match_obj(match_obj): if match_obj is None: print('Regex Match Fail!') else: print('Regex Match Success!', match_obj) if __name__ == '__main__': p = re.compile(r'[a-z]+') display_match_obj(p.match('hello')) display_match_obj(p.match('hello123')) display_match_obj(p.fullmatch('hello')) display_match_obj(p.fullmatch('hello123')) display_match_obj(p.match('123hello')) display_match_obj(p.match('123hello', 3))
输出结果:
Regex Match Success! <_sre.SRE_Match object; span=(0, 5), match='hello'> Regex Match Success! <_sre.SRE_Match object; span=(0, 5), match='hello'> Regex Match Success! <_sre.SRE_Match object; span=(0, 5), match='hello'> Regex Match Fail! Regex Match Fail! Regex Match Success! <_sre.SRE_Match object; span=(3, 8), match='hello'>
分析:
- '[a-z]+'能与'hello'和'hello123'的开头部分匹配
- '[a-z]+'能与'hello'彻底匹配
- '[a-z]+'不能与'hello123'彻底匹配
- '[a-z]+'不能与'123hello'的开头部分匹配
- '[a-z]+'能与'123hello'的切片'123hello'[3:]的开头部分匹配
上面使用的是正则表达式对象的match()和fullmatch()方法,咱们也能够经过re提供的模块级别的函数来实现:
if __name__ == '__main__': p = re.compile(r'[a-z]+') display_match_obj(re.match(p, 'hello')) display_match_obj(re.match(p, 'hello123')) display_match_obj(re.fullmatch(p, 'hello')) display_match_obj(re.fullmatch(p, 'hello123')) display_match_obj(re.match(p, '123hello')) display_match_obj(re.match(p, '123hello'[3:])) # 惟一不一样的是这里
输出结果跟上面是同样的
re模块的match()和fullmatch()函数不支持pos和endpos参数,因此只能经过字符串切片先对字符串进行切割。
再来看个对比:
if __name__ == '__main__': display_match_obj(re.match(r'[a-z]+', 'hello123')) display_match_obj(re.match(r'[a-z]+$', 'hello123')) display_match_obj(re.match(r'^[a-z]+$', 'hello123'))
输出结果:
Regex Match Success! <_sre.SRE_Match object; span=(0, 5), match='hello'> Regex Match Fail! Regex Match Fail!
分析:
- re.match()和re.fullmatch()中的pattern参数能够是正则表达式对象,也能够是Python字符串
- re.match()函数中的正则表达式参数加上边界元字符'^'和'$'就至关于re.fullmatch()了
- 当匹配过程是从字符串的第一个字符开始匹配时re.match(r'^[a-z]+$', 'hello123') 与 re.match(r'[a-z]+$', 'hello123')效果是同样的,由于re.match()原本就是从字符串开头开始匹配的;可是,若是匹配过程不是从字符串的第一个字符开始匹配时,它们是有区别的,具体请看下面这个例子。
if __name__ == '__main__': p1 = re.compile(r'[a-z]+$') p2 = re.compile(r'^[a-z]+$') display_match_obj(p1.match('123hello', 3)) display_match_obj(p2.match('123hello', 3))
输出结果:
Regex Match Success! <_sre.SRE_Match object; span=(3, 8), match='hello'> Regex Match Fail!
这个很好理解,由于元字符'^'匹配的是表示字符串开始位置的特殊字符,而不是字符串内容的第一个字符。match()匹配的是字符串内容的第一个字符,所以即便是在
MULTILINE
模式,re.match()也将只匹配字符串的开始位置,而不是该字符串每一行的行首。
内容查找,意思是经过特定的正则表达式对一个指定的字符串的内容进行扫描来判断该字符串中是否包含与这个正则表达式相匹配的内容。
经过re模块执行匹配测试时可使用的函数或方法是:
这个例子中,咱们来看下search()函数的使用,以及它与match()函数的对比。
Python提供了两个不一样的基于正则表达式的简单操做:
import re string = 'abcdef' print(re.match(r'c', string)) print(re.search(r'c', string))
输出结果:
None <_sre.SRE_Match object; span=(2, 3), match='c'>
这个比较简单,不作过多解析。
咱们应该可以想到,当正则表达式以'^'开头时,search()也会从字符串的开头进行匹配:
import re string = 'abcdef' print(re.match(r'c', string)) print(re.search(r'^c', string)) print(re.search(r'^a', string))
输出结果:
None None <_sre.SRE_Match object; span=(0, 1), match='a'>
这里再重复一下上面提到过的内容,就是元字符'^'与match()的从字符串开始位置开始匹配并非彻底等价的。由于元字符'^'匹配的是表示字符串开始位置的特殊字符,而不是字符串内容的第一个字符。match()匹配的是字符串内容的第一个字符,所以即便是在
MULTILINE
模式,re.match()也将只匹配字符串的开始位置,而不是该字符串每一行的行首;相关search('^...')却能够匹配每一行的行首。
下面来看个例子:
string = ''' A B X ''' print(re.match(r'X', string, re.MULTILINE)) print(re.search(r'^X', string, re.MULTILINE))
输出结果:
None <_sre.SRE_Match object; span=(5, 6), match='X'>
findall()与finditer()也是用来查找一个字符串中与正则表达式相匹配的内容,可是从名字上就能看出来,findall()与finditer()会讲这个字符串中全部与正则表达式匹配的内容都找出来,而search()仅仅是找到第一个匹配的内容。另外findall()返回的是全部匹配到的子串所组成的列表,而finditer()返回的是一个迭代器对象,该迭代器对象会将每一次匹配到的结果都做为一个匹配对象返回。下面来看一个例子:
尝试找出一个字符串中的全部副词(英语的副词一般都是以字母‘ly’结尾):
import re text = "He was carefully disguised but captured quickly by police." print(re.findall(r"\w+ly", text))
输出结果:
['carefully', 'quickly']
若是咱们想要获取关于全部匹配内容的更多信息,而不只仅是文本信息的话,就可使用finditer()函数。finditer()能够提供与各个匹配内容相对应的匹配对象,而后咱们就能够经过这个匹配对象的方法和属性来获取咱们想要的信息。
咱们来尝试获取一个字符串中全部副词以及它们各自在字符串中的切片位置:
import re text = "He was carefully disguised but captured quickly by police." for m in re.finditer(r"\w+ly", text): print("%02d-%02d: %s" % (m.start(), m.end(), m.group()))
输出结果:
07-16: carefully 40-47: quickly
传统的字符串操做只能替换明确指定的子串,而使用正则表达式默认是对一个字符串中全部与正则表达式相匹配的内容进行替换,也能够指定替换次数。
sub函数返回的是被替换后的字符串,若是字符串中没有与正则表达式相匹配的内容,则返回原始字符串;subn函数除了返回被替换后的字符串,还会返回一个替换次数,它们是以元组的形式返回的。下面来看个例子:将一个字符串中的全部的'-'字符删除
import re text = 'pro----gram-files' print(re.sub(r'-+', '', text)) print(re.subn(r'-+', '', text))
输出结果:
programfiles ('programfiles', 2)
说明: 被替换的内容(repl参数)能够是一个字符串,还能够是一个函数名。该函数会在每一次匹配时被调用,且该函数接收的惟一的参数是当次匹配相对应的匹配对象,经过这个函数咱们能够来作一些逻辑更加复杂的替换操做。
好比,上面那个例子中,若是咱们想要获得'program files'这个结果,咱们就须要把多个连续的'-'和单个'-'分别替换为 空字符串 和 一个空白字符:
def dashrepl(match_obj): if match_obj.group() == '-': return ' ' else: return '' if __name__ == '__main__': text = 'pro----gram-files' print(re.sub(r'-+', dashrepl, text)) print(re.subn(r'-+', dashrepl, text))
输出结果:
program files ('program files', 2)
说明: 当被替换的内容(repl参数)是一个字符串时可使用'\1'、'\g<NAME>'来引用正则表达式中的捕获组所匹配到的内容,可是须要注意的是这个字符串必须带上r前缀。
下面来看个例子:把一个函数名前面加上'py_'前缀
p = r'def\s+([A-Za-z_]\w*)\s*\((?P<param>.*)\)' repl = r'def py_\1(\g<param>)' text = 'def myfunc(*args, **kwargs):' print(re.sub(p, repl, text))
输出结果:
def py_myfunc(*args, **kwargs):
经过正则表达式对字符串进行分割的过程是:扫描整个字符串,查找与正则表达式匹配的内容,而后以该内容做为分割符对字符串进行分割,最终返回被分割后的子串列表。这对于将文本数据转换为Python易于读取和修改的结构化数据很是有用。
咱们能够经过maxsplit参数来限制最大切割次数。
print(re.split(r'\W+', 'Words, words, words.')) print(re.split(r'\W+', 'Words, words, words.', 1)) print(re.split(r'[a-f]+', '0a3B9', flags=re.IGNORECASE))
输出结果:
['Words', 'words', 'words', ''] ['Words', 'words, words.'] ['0', '3', '9']
分析:
- 第一行代码中,一共分割了3次(分隔符分别为:两个', '和一个'.'),所以返回的列表中有4个元素;
- 第二行代码中,限制了最大分割次数为1,所以返回的列表中只有2个元素;
- 第三行代码中,指定分隔符为一个或多个连续的小写字字母,可是指定的flag为忽略大小写,所以大写字母也能够做为分隔符使用;那么从小写字母'a'和大写字母'B'分别进行切割,因此返回的列表中有3个元素。
print(re.split(r'(\W+)', 'Words, words, words.')) print(re.split(r'x*', 'abcxde')) print(re.split(r'^$', '\nfoo\nbar\n', re.M))
输出结果:
['Words', ', ', 'words', ', ', 'words', '.', ''] C:\Python35\lib\re.py:203: FutureWarning: split() requires a non-empty pattern match. return _compile(pattern, flags).split(string, maxsplit) ['abc', 'de'] Traceback (most recent call last): File "C:/Users/wader/PycharmProjects/PythonPro/regex2.py", line 68, in <module> print(re.split(r'^$', '\nfoo\nbar\n', re.M)) File "C:\Python35\lib\re.py", line 203, in split return _compile(pattern, flags).split(string, maxsplit) ValueError: split() requires a non-empty pattern match.
咱们如今要使用Python正则表达式的字符串分割功能实现一个电话簿,具体流程是:从一个给定的文本中读取非空的行,而后把这些非空行进行切割获得相关信息。
text = """Ross McFluff: 834.345.1254 155 Elm Street Ronald Heathmore: 892.345.3428 436 Finley Avenue Frank Burger: 925.541.7625 662 South Dogwood Way Heather Albrecht: 548.326.4584 919 Park Place""" entries = re.split(r'\n+', text) phonebook = [re.split(r':?\s+', entry, 3) for entry in entries] print(phonebook)
输出结果:
[ ['Ross', 'McFluff', '834.345.1254', '155 Elm Street'], ['Ronald', 'Heathmore', '892.345.3428', '436 Finley Avenue'], ['Frank', 'Burger', '925.541.7625', '662 South Dogwood Way'], ['Heather', 'Albrecht', '548.326.4584', '919 Park Place'] ]
说明:实际输出中是没有这样的缩进格式的,这里只是为了方便你们查看。
分析:
- 上面这个例子整体上有两个分割过程,第一个分割是经过换行符为分割符获得全部的非空行列表,第二个分割是对获得的每一行文本进行切割,获得每个联系人的相关信息;
- 每一行的内容分别为:firstname, lastname, tel, addr;
- 因为lastname与tel之间包含一个冒号,而其余内容之间只是包含空白字符,因此用于做为分隔符的正则表达式应该是':?\s+';
- 因为最后的那个addr是由多个段组成,且这些段之间也有空白字符,此时能够利用maxsplit参数限定最大分割次数,从而使得最后的3个字段做为一个addr总体返回。
当咱们经过re.match或re.search函数获得一个匹配对象m后,能够经过if m is None
来判断是否匹配成功。在匹配成功的条件下,咱们可能还想要获取匹配的值。Python中的匹配对象主要是以“组”的形式来获取匹配内容的,下面咱们来看下具体操做:咱们如今要经过一个正则表达式获取一个字符串中的'姓名'、'年龄'和'手机号码'
import re p = re.compile(r'.*name\s+is\s+(\w+).*am\s+(?P<age>\d{1,3})\s+years.*tel\s+is\s+(?P<tel>\d{11}).*', re.DOTALL) string = ''' My name is Tom, I am 16 years old, My tel is 13972773480. ''' m = re.match(p, string) # 或 # m = p.match(string) if m is None: print('Regex match fail.') else: result = ''' name: %s age: %s tel: %s ''' print(result % (m.group(1), m.group(2), m.group('tel')))
输出结果:
name: Tom age: 16 tel: 13972773480
分析:
- 因为要匹配的字符串中包括换行符,为了让元字符'.'可以匹配换行符,因此编译正则表达式须要指定re.DOTALL这个flag;
- 调用匹配对象的方法或属性前须要判断匹配是否成功,若是匹配失败,获得的匹配对象将是None,其方法和属性调用会报错;
- 对于"非命名捕获组"只能经过分组索引数字来获取其匹配到的内容,如m.group(1);而对于命名捕获组既能够经过分组索引数字来获取其匹配到的内容,如m.group(2),也能够经过分组名称来获取其匹配到的内容,如m.group('tel')。
咱们在上面介绍匹配对象所提供的方法时对expand方法进行过说明:
match.expand(template)方法可用于经过获得的匹配对象来构造并返回一个新的字符串,template是一个字符串,用于指定新字符串的格式;从Python 3.5开始,未匹配到的分组将会替换为一个空字符串
这里咱们用它来实现上面这个例子的输出效果,你们体会下它的功能:
import re p = re.compile(r'.*name\s+is\s+(\w+).*am\s+(?P<age>\d{1,3})\s+years.*tel\s+is\s+(?P<tel>\d{11}).*', re.DOTALL) string = ''' My name is Tom, I am 16 years old, My tel is 13972773480. ''' # m = re.match(p, string) # 或 m = p.match(string) if m is None: print('Regex match fail.') else: template_str = ''' name: \g<1> age: \g<2> tel: \g<tel> ''' print(m.expand(template_str))
输出结果,跟上面是同样的。
这里,咱们主要是想经过对匹配对象各个方法和属性的调用,让你们更深入的理解经过这些方法和属性咱们能够获得什么。
p = re.compile(r'.*name\s+is\s+(\w+).*am\s+(?P<age>\d{1,3})\s+years.*tel\s+is\s+(?P<tel>\d{11}).*', re.DOTALL) string = ''' My name is Tom, I am 16 years old, My tel is 13972773480. ''' # m = re.match(p, string) # 或 m = p.match(string) if m is None: print('Regex Match Fail!') else: print('match_obj.group(): ', '匹配到的全部内容: ', m.group()) print('match_obj.group(0): ', '同上: ', m.group(0)) print('match_obj.group(1): ', '第一个捕获组匹配到的内容: ', m.group(1)) print('match_obj.group(2): ', '第二个命名捕获组匹配到的内容: ', m.group(2)) print('match_obj.group("tel"): ', '第三个命名捕获组匹配到的内容: ', m.group('tel')) print('match_obj.groups(): ', '全部捕获组匹配到的内容组成的元组对象: ', m.groups()) print('match_obj.groupdict(): ', '全部命名捕获组匹配到的内容组成的字典对象: ', m.groupdict()) print('match_obj: ', '直接打印匹配对象: ', m) print('match_obj.string: ', '传递给re.match函数的字符串参数: ', m.string) print('match_obj.re: ', '传递给re.match函数的正则表达式对象: ', m.re) print('match_obj.pos, match_obj.endpos: ', '传递个match方法的pos和endpos参数: ', m.pos, m.endpos) print('match_obj.pos, match_obj.start(1), match_obj.end(1): ', '第一个捕获组匹配的内容在字符串中的切片位置: ', m.start(1), m.end(1)) print('match_obj.pos, match_obj.span(1): ', '第一个捕获组匹配的内容在字符串中的切片位置: ', m.span(1)) print('match_obj.pos, match_obj.start("tel"), match_obj.end("tel"): ', '命名捕获组tel匹配的内容在字符串中的切片位置: ', m.start('tel'), m.end('tel')) print('match_obj.pos, match_obj.span(tel): ', '命名捕获组tel匹配的内容在字符串中的切片位置: ', m.span('tel'))
输出结果:
match_obj.group(): 匹配到的全部内容: My name is Tom, I am 16 years old, My tel is 13972773480. match_obj.group(0): 同上: My name is Tom, I am 16 years old, My tel is 13972773480. match_obj.group(1): 第一个捕获组匹配到的内容: Tom match_obj.group(2): 第二个命名捕获组匹配到的内容: 16 match_obj.group("tel"): 第三个命名捕获组匹配到的内容: 13972773480 match_obj.groups(): 全部捕获组匹配到的内容组成的元组对象: ('Tom', '16', '13972773480') match_obj.groupdict(): 全部命名捕获组匹配到的内容组成的字典对象: {'age': '16', 'tel': '13972773480'} match_obj: 直接打印匹配对象: <_sre.SRE_Match object; span=(0, 75), match='\n My name is Tom,\n I am 16 years old,\n > match_obj.string: 传递给re.match函数的字符串参数: My name is Tom, I am 16 years old, My tel is 13972773480. match_obj.re: 传递给re.match函数的正则表达式对象: re.compile('.*name\\s+is\\s+(\\w+).*am\\s+(?P<age>\\d{1,3})\\s+years.*tel\\s+is\\s+(?P<tel>\\d{11}).*', re.DOTALL) match_obj.pos, match_obj.endpos: 传递个match方法的pos和endpos参数: 0 75 match_obj.pos, match_obj.start(1), match_obj.end(1): 第一个捕获组匹配的内容在字符串中的切片位置: 16 19 match_obj.pos, match_obj.span(1): 第一个捕获组匹配的内容在字符串中的切片位置: (16, 19) match_obj.pos, match_obj.start("tel"), match_obj.end("tel"): 命名捕获组tel匹配的内容在字符串中的切片位置: 58 69 match_obj.pos, match_obj.span(tel): 命名捕获组tel匹配的内容在字符串中的切片位置: (58, 69)
前面咱们介绍re模块使用步骤时,第一步就是“编写一个表示正则表达式规则的Python字符串”,那么正则表达式与Python字符串之间是什么关系呢?另外,咱们一般都在一个正则表达式字符串前加上一个前缀r是何用意呢?
由于正则表达式并非Python语言的核心部分(有些应用程序根本就不须要正则表达式,re模块就像socket或zlib同样,只是包含在Python中的一个简单的C语言扩展模块),也没有建立专门的语法来表示它们,因此在Python中正则表达式是以Python字符串的形式来编写并进行处理的。可是,咱们应该知道的是字符串有 字符串的字面值(咱们给一个字符串赋的值) 和 字符串的实际值(这个字符串的打印值),且正则表达式引擎把一个字符串做为正则表达式处理时,所取的是这个字符串的实际值,而不是它的字面值。
字符串的字面值 与 字符串的实际值 不必定是等价的,由于有些字符组合起来表示的是一个特殊的符号,此时会致使正则表达式错误而没法匹配到想要的结果。好比:\1在正则表达式中表示引用第一个捕获组所匹配到的内容,而在字符串中却表示一个特殊的字符,所以若是一个表示正则表达式的字符串中包含"\1"且没有作任何处理的话,它是没法匹配到咱们想要的结果的。以下图所示:
若是想要匹配正确结果就须要对该字符串中表示特殊字符的字符组合进行处理,咱们只须要在'\1'前加一个反斜线对'\1'中的反斜线进行转义就能够了,即字符串"\1"的字面值才是\1。以下图所示:
也就是说,一个正则表达式要想用一个Python字符串来表示时,可能须要对这个字符串的字面值作一些特殊的转义处理。
如今反过来考虑,\s在正则表达式中表示空白字符,若是咱们想匹配一个字符串中'\s'这两个字符,就须要在正则表达式中\s前也加一个反斜线进行转义\\s
。那么这时候表示正则表达式的字符串一样不能直接写'\\s'
,由于它是字符串字面值,其实际值是\s,所以此时也是不能匹配到咱们想要的结果的。以下图所示:
若是想要匹配正确结果就须要对该字符串进行一些处理,咱们须要在'\\s'
前加两个反斜线对'\\s'
中的两个反斜线分别进行转义,即字符串"\\\\s"
的字面值才是\\s
。以下图所示:
经过上面两个问题咱们能够得出如下结论:
怎么办呢?忘掉上面这全部的问题吧,你只须要在表示正则表达式的字符串前加上一个前缀r就能够把这个字符串固然正则表达式来写了。 r是Raw,即“原始”的意思,带有r前缀的字符串就叫作“Raw String”,即原始字符串的意思。也就是说当一个字符串带有r前缀时,它的字面值就是其真实值,也就是正则表达式的值。
简单来讲,表示正则表达式匹配规则的字符串前面的r前缀是为了解决字符串中的反斜线与正则表达式中的反斜线引发的冲突问题。另外,咱们根本无需关心哪些字符会引发这样的冲突,只须要在每一个表示正则表达式的字符串前加上一个r前缀就能够了。
假设你正在写一个扑克游戏,每张牌分别用一个字符来表示:
首先,咱们先来写一个工具函数来帮助咱们更优雅的展现匹配结果:
def display_match_obj(match_obj): if match_obj is None: return None return '<Match: %r, groups=%r>' % (match_obj.group(), match_obj.groups())
游戏规则有两个:
正确的正则表达式应该是这样的:'^[akqjt2-9]{5}$'
下面来看下面几个匹配测试
import re p = re.compile(r"^[atjqk2-9]{5}$") display_match_obj(p.match("akt5q")) display_match_obj(p.match("akt5e")) display_match_obj(p.match("akt")) display_match_obj(p.match("727ak")) display_match_obj(p.match("aaaak"))
输出结果:
<Match: 'akt5q', groups=()> Invalid Invalid <Match: '727ak', groups=()> <Match: 'aaaak', groups=()>
分析:
这里,咱们先不考虑上面的规则,仅仅考虑须要包含两张同样的牌。
正确的正则表达式应该是这样的:'.*(.).*\1.*'
下面来看下面几个匹配测试
import re p = re.compile(r".*(.).*\1.*") display_match_obj(p.match("akt5q")) display_match_obj(p.match("akt5e")) display_match_obj(p.match("akt")) display_match_obj(p.match("727ak")) display_match_obj(p.match("aaaak"))
输出结果:
Invalid Invalid Invalid <Match: '727ak', groups=('7',)> <Match: 'aaaak', groups=('a',)>
分析:
其实很简单,只要重复相应次数的 '\1.*'
就能够了:
'.*(.).*\1.*\1.*'
'.*(.).*\1.*\1.*\1.*'
这个要求的难点在于,这两个匹配需求的匹配规则相对独立没法用一个常规的正则表达式匹配模式来表达。要把多个相对独立的正则表达式整合到一块儿,咱们有两种实现方式:
import re def process_match(string): p1 = re.compile(r"^[akqjt2-9]{5}$") if p1.match(string): p2 = re.compile(r".*(.).*\1.*") display_match_obj(p2.match(string)) else: display_match_obj(None) if __name__ == "__main__": process_match("727a") process_match("727akk") process_match("72tak") process_match("727ak")
输出结果:
Invalid Invalid Invalid <Match: '727ak', groups=('7',)>
分析:
import re p = re.compile(r"(?=^[akqjt2-9]{5}$)(?=.*(.).*\1.*)") display_match_obj(p.match("727a")) display_match_obj(p.match("727akk")) display_match_obj(p.match("72tak")) display_match_obj(p.match("727ak"))
输出结果:
Invalid Invalid Invalid <Match: '', groups=('7',)>
分析:
- 由输出结果可知,一样也是最后一个匹配成功,所以这个正则表达式
'(?=^[akqjt2-9]{5}$)(?=.*(.).*\1.*)'
是知足要求的;- 另外,发现Match的结果为空,这是由于特殊构造
(?=...)
在匹配过程当中是不消费字符的,这也就说明这种特殊构造只适合作匹配测试,不能获取匹配到的内容。
问题交流群:666948590