Python系列之正则表达式详解
Python 正则表达式模块 (re) 简介
Python 的 re 模块(Regular Expression 正则表达式)提供各种正则表达式的匹配操作,和 Perl 脚本的正则表达式功能类似,使用这一内嵌于 Python 的语言工具,尽管不能满足所有复杂的匹配情况,但足够在绝大多数情况下能够有效地实现对复杂字符串的分析并提取出相关信息。Python 会将正则表达式转化为字节码,利用 C 语言的匹配引擎进行深度优先的匹配。
表 1. 正则表达式元字符和语法
符号 | 说明 | 实例 |
---|---|---|
. | 表示任意字符,如果说指定了 DOTALL 的标识,就表示包括新行在内的所有字符。 | \’abc\’ >>>\’a.c\’ >>>结果为:\’abc\’ |
^ | 表示字符串开头。 | \’abc\’ >>>\’^abc\’ >>>结果为:\’abc\’ |
$ | 表示字符串结尾。 | \’abc\’ >>>\’abc$\’ >>>结果为:\’abc\’ |
*, +, ? | \’*\’表示匹配前一个字符重复 0 次到无限次,\’+\’表示匹配前一个字符重复 1次到无限次,\’?\’表示匹配前一个字符重复 0 次到1次 |
\’abcccd\’ >>>\’abc*\’ >>>结果为:\’abccc\’ \’abcccd\’ >>>\’abc+\’ >>>结果为:\’abccc\’ \’abcccd\’ >>>\’abc?\’ >>>结果为:\’abc\’ |
*?, +?, ?? | 前面的*,+,?等都是贪婪匹配,也就是尽可能多匹配,后面加?号使其变成惰性匹配即非贪婪匹配 |
\’abc\’ >>>\’abc*?\’ >>>结果为:\’ab\’ \’abc\’ >>>\’abc??\’ >>>结果为:\’ab\’ \’abc\’ >>>\’abc+?\’ >>>结果为:\’abc\’ |
{m} | 匹配前一个字符 m 次 | \’abcccd\’ >>>\’abc{3}d\’ >>>结果为:\’abcccd\’ |
{m,n} | 匹配前一个字符 m 到 n 次 | \’abcccd\’ >>> \’abc{2,3}d\’ >>>结果为:\’abcccd\’ |
{m,n}? | 匹配前一个字符 m 到 n 次,并且取尽可能少的情况 | \’abccc\’ >>> \’abc{2,3}?\’ >>>结果为:\’abcc\’ |
\ | 对特殊字符进行转义,或者是指定特殊序列 | \’a.c\’ >>>\’a\.c\’ >>> 结果为: \’a.c\’ |
[] | 表示一个字符集,所有特殊字符在其都失去特殊意义,只有: ^ – ] \ 含有特殊含义 | \’abcd\’ >>>\’a[bc]\’ >>>结果为:\’ab\’ |
| | 或者,只匹配其中一个表达式 ,如果|没有被包括在()中,则它的范围是整个正则表达式 | \’abcd\’ >>>\’abc|acd\’ >>>结果为:\’abc\’ |
( … ) | 被括起来的表达式作为一个分组. findall 在有组的情况下只显示组的内容 | \’a123d\’ >>>\’a(123)d\’ >>>结果为:\’123\’ |
(?#…) | 注释,忽略括号内的内容 特殊构建不作为分组 | \’abc123\’ >>>\’abc(?#fasd)123\’ >>>结果为:\’abc123\’ |
(?= … ) | 表达式’…’之前的字符串,特殊构建不作为分组 | 在字符串’ pythonretest ’中 (?=test) 会匹配’ pythonre ’ |
(?!…) | 后面不跟表达式’…’的字符串,特殊构建不作为分组 | 如果’ pythonre ’后面不是字符串’ test ’,那么 (?!test) 会匹配’ pythonre ’ |
(?<= … ) | 跟在表达式’…’后面的字符串符合括号之后的正则表达式,特殊构建不作为分组 | 正则表达式’ (?<=abc)def ’会在’ abcdef ’中匹配’ def ’ |
(?:) | 取消优先打印分组的内容 | \’abc\’ >>>\'(?:a)(b)\’ >>>结果为\'[b]\’ |
?P<> | 指定Key | \’abc\’ >>>\'(?P<n1>a)>>>结果为:groupdict{n1:a} |
表 2. 正则表达式特殊序列
特殊表达式序列 | 说明 |
---|---|
\A | 只在字符串开头进行匹配。 |
\b | 匹配位于开头或者结尾的空字符串 |
\B | 匹配不位于开头或者结尾的空字符串 |
\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_] |
\Z | 只在字符串结尾进行匹配 |
上面提到贪婪匹配和非贪婪匹配请看例子:
import re #贪婪 ret_greed= re.findall(r\'a(\d+)\',\'a23b\') print(ret_greed) #非贪婪 ret_no_greed= re.findall(r\'a(\d+?)\',\'a23b\') print(ret_no_greed) [\'23\'] [\'2\']
由于贪婪匹配为尽可能的多匹配所以结果为23 ,有人好奇了,findall是什么鬼 ,请耐心往下看:
re模块
正则表达式使用反斜杠” \ “来代表特殊形式或用作转义字符,这里跟Python的语法冲突,因此,Python用” \\ “表示正则表达式中的” \ “,因为正则表达式中如果要匹配” \ “,需要用\来转义,变成” \ “,而Python语法中又需要对字符串中每一个\进行转义,所以就变成了” \\ “。
上面的写法是不是觉得很麻烦,为了使正则表达式具有更好的可读性,Python特别设计了原始字符串(raw string),需要提醒你的是,在写文件路径的时候就不要使用raw string了,这里存在陷阱。raw string就是用’r’作为字符串的前缀,如 r”\n”:表示两个字符”\”和”n”,而不是换行符了。Python中写正则表达式时推荐使用这种形式。
1、 re.findall(pattern, string[, flags]):
方法能够以列表的形式返回能匹配的子串。先看简单的例子:
import re a = \'one1two2three3four4\' ret = re.findall(r\'(\d+)\',a) print(ret) [\'1\', \'2\', \'3\', \'4\']
从上面的例子可以看出返回的值是个列表,并且返回字符串中所有匹配的字符串。
2、re.finditer(pattern, string[, flags])
搜索string,返回一个顺序访问每一个匹配结果(Match对象)的迭代器。 请看例子:
import re p = re.compile(r\'\d+\') for m in p.finditer(\'one1two2three3four4\'): print m.group(), ### output ### # 1 2 3 4
3、re.match和re.search
Python提供了两种不同的原始操作:match和search。match是从字符串的起点开始做匹配,而search(perl默认)是从字符串做任意匹配。看个例子:
import re ret_match= re.match("c","abcde"); #从字符串开头匹配,匹配到返回match的对象,匹配不到返回None if(ret_match): print("ret_match:"+ret_match.group()); else: print("ret_match:None"); ret_search = re.search("c","abcde"); #扫描整个字符串返回第一个匹配到的元素并结束,匹配不到返回None if(ret_search): print("ret_search:"+ret_search.group()); ret_match:None ret_search:c
re.match对象拥有以下方法:
import re a = "123abc456" ret_match= re.match("a","abcde"); print(ret_match.group()) #返回返回被 RE 匹配的字符串 print(ret_match.start()) #返回匹配开始的位置 print(ret_match.end()) #返回匹配结束的位置 print(ret_match.span()) #返回一个元组包含匹配 (开始,结束) 的位置
其中group()方法可以指定组号,如果组号不存在则返回indexError异常看如下例子:
import re a = "123abc456" re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(0) #123abc456,返回整体默认返回group(0) re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(1) #123 re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(2) #abc re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(3) #456
4、re.sub和re.subn
两种方法都是用来替换匹配成功的字串,值得一提的时,sub不仅仅可以是字符串,也可以是函数。subn函数返回元组,看下面例子:
import re #sub ret_sub = re.sub(r\'(one|two|three)\',\'ok\',\'one word two words three words\') #ok word ok words ok words #subn import re ret_subn = re.subn(r\'(one|two|three)\',\'ok\',\'one word two words three words\') #(\'ok word ok words ok words\', 3) 3,表示替换的次数
5、re.split(pattern, string, maxsplit=0)
通过正则表达式将字符串分离。如果用括号将正则表达式括起来,那么匹配的字符串也会被列入到list中返回。maxsplit是分离的次数,maxsplit=1分离一次,默认为0,不限制次数。看一下例子:
import re ret = re.split(\'\d+\',\'one1two2three3four4\') #匹配到1的时候结果为\'one\'和\'two2three3four4\',匹配到2的时候结果为\'one\', \'two\'和\'three3four4\', 所以结果为: ####output#### [\'one\', \'two\', \'three\', \'four\', \'\']
6、re.compile(strPattern[, flag])
这个方法是Pattern类的工厂方法,用于将字符串形式的正则表达式编译为Pattern对象。第二个参数flag是匹配模式,取值可以使用按位或运算符’|’表示同时生效,比如re.I | re.M。另外,你也可以在regex字符串中指定模式,比如re.compile(‘pattern’, re.I | re.M)与re.compile(‘(?im)pattern’)是等价的。可选值有:
re.I(IGNORECASE): 忽略大小写(括号内是完整写法,下同) re.M(MULTILINE): 多行模式,改变\'^\'和\'$\'的行为(参见上图) re.S(DOTALL): 点任意匹配模式,改变\'.\'的行为 re.L(LOCALE): 使预定字符类 \w \W \b \B \s \S 取决于当前区域设定 re.U(UNICODE): 使预定字符类 \w \W \b \B \s \S \d \D 取决于unicode定义的字符属性 re.X(VERBOSE): 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释。
请看例子:
import re text = "JGood is a handsome boy, he is cool, clever, and so on..." regex = re.compile(r\'\w*oo\w*\') print(regex.findall(text)) [\'JGood\', \'cool\']
re 小节
#re.match() #re.search() #re.findall() #re.split() #re.sub()
re.match()、re.search()可以分为两种情况,第一种不分组第二种分组(方便记忆):
a = \'hello word diao zha tian\' ret = re.match(\'h\w+\',a) print(ret.group()) #获取匹配到的所有结果 print(ret.groups()) # 获取模型中匹配到的分组情况 print(ret.groupdict())# 获取模型中匹配到的分组结果 ############output############## hello () {} ret = re.match(\'(?P<n>h)(?P<n1>\w+)\',a) print(ret) print(ret.group()) #获取匹配到的所有结果 print(ret.groups()) # 获取模型中匹配到的分组情况 print(ret.groupdict())# 获取模型中匹配到的分组结果 #?P<n1> = Key ############output############## <_sre.SRE_Match object; span=(0, 5), match=\'hello\'> hello (\'h\', \'ello\') {\'n\': \'h\', \'n1\': \'ello\'}
View Code
re.findall()
a = \'hello alex alex adn acd\' n = re.findall(\'(a)(\w+)\',a) #[(\'a\', \'lex\'), (\'a\', \'lex\'), (\'a\', \'dn\'), (\'a\', \'cd\')] print(n) #从左到右,从外到内 ,?P<> 无效
View Code
re.split()
在编写计算器的时候可以用re.split() 比如:
def f1(ex): return eval(ex) #测试用 真实中要自己编写四则运算 a = \'1*2+(5/6)+(12*23)/15\' while True: ret = re.split(\'\(([^()]+)\)\', a, 1) if len(ret) == 3: a,b,c = re.split(\'\(([^()]+)\)\', a, 1) rec = f1(b) a = a + str(rec) + c else: red = f1(a) print(red) break
re匹配括号
re.sub() 用于替换匹配的字符串
content = "123abc456" new_content = re.sub(\'\d+\', \'sb\', content) # new_content = re.sub(\'\d+\', \'sb\', content, 1) print new_content
View Code
常用的验证规则:
验证手机号: (^(13\d|14[57]|15[^4\D]|17[13678]|18\d)\d{8}|170[^346\D]\d{7}) 验证邮箱: ^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$ IP: ^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$
View Code
计算器源码:
import re def md(date_list,symbol): \'\'\' :param date_list: 匹配到的表达式 :param symbol: 符号 :return: 乘数计算得到的值 \'\'\' a = date_list.index(symbol) #取到符号 if symbol == \'*\' and date_list[a + 1] != \'-\': #如果是乘号并且索引的下一个位置不是负号计算 k = float(date_list[a - 1]) * float(date_list[a + 1]) elif symbol == \'/\' and date_list[a + 1] != \'-\': #如果是除号并且索引的下一个位置不是负号计算 k = float(date_list[a - 1]) / float(date_list[a + 1]) elif symbol == \'*\' and date_list[a + 1] == \'-\': #如果是乘号并且索引的下一个位置是负号计算 k = -(float(date_list[a - 1]) * float(date_list[a + 2])) elif symbol == \'/\' and date_list[a + 1] == \'-\': #如果是除号并且索引的下一个位置是负号计算 k = -(float(date_list[a - 1]) / float(date_list[a + 2])) del date_list[a - 1], date_list[a - 1], date_list[a - 1] #删除列表里参与计算的索引位置 date_list.insert(a - 1, str(k)) #把新的值插入到列表中 return date_list #处理混乱的四则,按照先算加减后乘除的原则 def fun(s): \'\'\' :param s: 去除括号后的表达式 :return: 表达式的返回值 \'\'\' list_str = re.findall(\'([\d\.]+|/|-|\+|\*)\',s) #匹配表达式 sum=0 while 1: if \'*\' in list_str and \'/\' not in list_str: #判断乘是否在表达式内 md(list_str, \'*\') elif \'*\' not in list_str and \'/\' in list_str: #判断乘是否在表达式内 md(list_str, \'/\') #调用md函数处理除号 elif \'*\' in list_str and \'/\' in list_str: a = list_str.index(\'*\') b = list_str.index(\'/\') if a < b: md(list_str, \'*\') else: md(list_str, \'/\') else: if list_str[0]==\'-\': #判断是否是负号 list_str[0]=list_str[0]+list_str[1] del list_str[1] sum += float(list_str[0]) for i in range(1, len(list_str), 2): if list_str[i] == \'+\' and list_str[i + 1] != \'-\': sum += float(list_str[i + 1]) elif list_str[i] == \'+\' and list_str[i + 1] == \'-\': sum -= float(list_str[i + 2]) elif list_str[i] == \'-\' and list_str[i + 1] == \'-\': sum += float(list_str[i + 2]) elif list_str[i] == \'-\' and list_str[i + 1] != \'-\': sum -= float(list_str[i + 1]) break return sum a=\'1 - 2 * ( (60-30 +(-40.0/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )\' #循环去除括号 while True: ret = re.split(\'\(([^()]+)\)\', a, 1) if len(ret) == 3: a,b,c = re.split(\'\(([^()]+)\)\', a, 1) rec = fun(b) a = a + str(rec) + c else: red = fun(a) print(red) break
View Code