【Python之路】特别篇--Python正则表达式
正则表达式的基础
正则表达式并不是Python的一部分。
正则表达式是用于处理字符串的强大工具,拥有自己独特的语法以及一个独立的处理引擎,效率上可能不如str自带的方法,但功能十分强大。
得益于这一点,在提供了正则表达式的语言里,正则表达式的语法都是一样的,区别只在于不同的编程语言实现支持的语法数量不同。
就其本质而言,正则表达式(或 RE)是一种小型的、高度专业化的编程语言,(在Python中)它内嵌在Python中,并通过 re 模块实现。
元字符
元字符: . ^ $ * + ? { [ ] \ | ( )
. 匹配除了换行符外所有字符 (通配符)
content = 'Abcdefghijklmnopq' test = re.findall(r"b.d",content) print(test) ['bcd']
^ 以....开头
content = 'Abcdefghijklmnopq' test = re.findall(r"^Abcd",content) print(test) ['Abcd']
$ 以....结尾
content = 'Abcdefghijklmnopq' test = re.findall(r"nopq$",content) print(test) ['nopq']
* 匹配0到多次 {0, } 控制它前面的字符
content = 'Abcdefghijklmnopq' test = re.findall(r"A.*e",content) print(test) ['Abcde']
+ 匹配1到多次 {1, }
content = 'abcdefab111111' test = re.findall(r"ab1+",content) print(test) ['ab111111']
? 匹配0到1次 {0,1}
content = 'abcdefab111111' test = re.findall(r"ab1?",content) print(test) ['ab', 'ab1']
* + 都是按照贪婪模式进行匹配 非贪婪模式 需要在后面加个?
content = 'abcdefab111111' test = re.findall(r"ab1+?",content) print(test) ['ab1'] re.search(r"a(\d+?)","a2345").group() => a2 re.search(r"a(\d*?)","a2345").group() => a #如果前后均有限定条件 ?不起作用 re.search(r"a(\d*?)b","a2345b").group() => a2345b
( ) 组 作为一个整体
content = 'abcdefab111111' test = re.findall(r"(ab1)",content) print(test) ['ab1']
{ } 重复次数自定义
content = 'abcdefab111111' test = re.findall(r"ab1{3,9}",content) print(test) ['ab111111']
[ ] 字符集 表示或
字符集里面元字符会失去意义 除了 - \ ^ 3个元字符外
content1 = 'wwwwwabdxxxxx' test1 = re.findall(r"a[bc]d",content1) print(test1) #['abd'] content2 = 'wwwwwacdxxxxx' test2 = re.findall(r"a[bc]d",content2) print(test2) #['acd'] *********************************************************************** content = 'wwwwwa.xxxxx' test = re.findall(r"a[.]x",content) print(test) #['a.x'] content = 'wwwww1234xxxxx' test = re.findall(r"[1-9]",content) #1~9的数字 print(test) #['1', '2', '3', '4'] content = 'wwwww1234xxxxx' test = re.findall(r"[^1-9]",content) #非1~9的数字 print(test) #['w', 'w', 'w', 'w', 'w', 'x', 'x', 'x', 'x', 'x']
\ 作用:
- 后面跟元字符去除特殊功能
- 后面跟普通字符实现特殊功能
- 引用序号对应的字组所匹配的字符串
test = re.search(r"(alex)(eric)com\2","alexericcomeric") print(test.group()) #alexericcomeric
\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]
\b 匹配一个单词边界,单词和空格间的位置 匹配特殊字符(不单止空格)
content = 'wwwww1234xxxxx' test = re.findall(r"\d",content) print(test) # ['1', '2', '3', '4'] content = 'ww&*#$%ww1234xx' test = re.findall(r"\D",content) print(test) # ['w', 'w', '&', '*', '#', '$', '%', 'w', 'w', 'x', 'x'] content = 'asdasd ' test = re.findall(r"\s",content) print(test) # [' ', ' ', ' '] content = ' asdasd ' test = re.findall(r"\S",content) print(test) # ['a', 's', 'd', 'a', 's', 'd'] content = 'abc123^&*lm-\_' test = re.findall(r"\w",content) print(test) # ['a', 'b', 'c', '1', '2', '3', 'l', 'm', '_'] content = 'abc123^&*lm-\_' test = re.findall(r"\W",content) print(test) # ['^', '&', '*', '-', '\\'] content = 'I like Sooooo' test = re.findall(r"like\b",content) print(test) # ['like'] ******************************************* test = re.findall(r"abc\b","asdasd abc ") test = re.findall(r"abc\b","asdasd abc*") print(test) # ['abc']
match()
match()
# match,从起始位置开始匹配,匹配成功返回一个对象,未匹配成功返回None match(pattern, string, flags=0) # pattern: 正则模型 # string : 要匹配的字符串 # falgs : 匹配模式 # re.I(re.IGNORECASE): 忽略大小写(括号内是完整写法,下同) # M(MULTILINE): 多行模式,改变'^'和'$'的行为 # S(DOTALL): 点任意匹配模式,改变'.'的行为 使 . 匹配包括换行在内的所有字符 # L(LOCALE): 使预定字符类 \w \W \b \B \s \S 取决于当前区域设定 # U(UNICODE): 使预定字符类 \w \W \b \B \s \S \d \D 取决于unicode定义的字符属性 # X(VERBOSE): 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释。以下两个正则表达式是等价的: ************************************* # match对象的方法 .group() 获取匹配到的所有结果 .groups() 获取模型中匹配到的分组结果 .groupdict() 获取模型中匹配到的分组中所有执行了key的组 .group() 返回被RE匹配的字符串 可以加参数group(1) 组号 .start() 返回匹配开始的位置 .end() 返回匹配结束的位置 .span() 返回一个元组包含的匹配的位置
# 无分组 r = re.match("h\w+", origin) print(r.group()) # 获取匹配到的所有结果 print(r.groups()) # 获取模型中匹配到的分组结果 print(r.groupdict()) # 获取模型中匹配到的分组结果 # 有分组 # 为何要有分组?提取匹配成功的指定内容(先匹配成功全部正则,再匹配成功的局部内容提取出来) r = re.match("h(\w+).*(?P<name>\d)$", origin) print(r.group()) # 获取匹配到的所有结果 print(r.groups()) # 获取模型中匹配到的分组结果 print(r.groupdict()) # 获取模型中匹配到的分组中所有执行了key的组
search()
search()
# search 匹配成功有结果,返回match对象 # 查看返回结果用.group() # search,浏览整个字符串去匹配第一个,未匹配成功返回None # search(pattern, string, flags=0)
# 无分组 r = re.search("a\w+", origin) print(r.group()) # 获取匹配到的所有结果 print(r.groups()) # 获取模型中匹配到的分组结果 print(r.groupdict()) # 获取模型中匹配到的分组结果 # 有分组 r = re.search("a(\w+).*(?P<name>\d)$", origin) print(r.group()) # 获取匹配到的所有结果 print(r.groups()) # 获取模型中匹配到的分组结果 print(r.groupdict()) # 获取模型中匹配到的分组中所有执行了key的组
findall()
findall()
# 优先取组里内容返回! # findall,获取非重复的匹配列表;如果有一个组则以列表形式返回,且每一个匹配均是字符串;如果模型中有多个组,则以列表形式返回,且每一个匹配均是元祖; # 空的匹配也会包含在结果中 # findall(pattern, string, flags=0) data = re.findall("\d+\w\d+",'a2b3c4d5') # ['2b3', '4d5'] # re.findall() 匹配成功一个后,从匹配成功最后位置开始下一次查找 # 空的匹配也会包含在结果中 data = re.findall("",'a2') print(data) # ['', '', ''] *********************************** #有几个括号就取几次 data = re.findall(r'(\dasd)*','1asd2asdp3asd3434') print(data) # ['2asd', '', '3asd', '', '', '', '', ''] # 贪婪匹配 第一段取到1asd2asd 但最后返回 2asd 取最后一个! 如下: a= "alex" data = re.findall(r'(\w)(\w)(\w)(\w)',a) print(data) # [('a', 'l', 'e', 'x')] data = re.findall(r'(\w){4}',a) print(data) # ['x'] => 只是执行了4次,返回还是按一个括号算,取最后匹配的一项 *********************************** test = re.findall("www.(baidu|laonanhai).com","asdsa www.baidu.com") print(test) # ['baidu'] 添加 ?: 去掉优先权 test = re.findall("www.(?:baidu|laonanhai).com","asdsa www.baidu.com") print(test) # ['www.baidu.com']
# 无分组 r = re.findall("a\w+",origin) print(r) # 有分组 origin = "hello alex bcd abcd lge acd 19" r = re.findall("a((\w*)c)(d)", origin) print(r) # [('bc', 'b', 'd'), ('c', '', 'd')]
sub()
sub()
# sub,替换匹配成功的指定位置字符串 sub(pattern, repl, string, count=0, flags=0) # pattern: 正则模型 # repl : 要替换的字符串或可执行对象 # string : 要匹配的字符串 # count : 指定匹配个数 # flags : 匹配模式 test = re.sub("g.t","have","I get A, I got B , I gut C") print(test) #I have A, I have B , I have C ******************************************** #subn 最后还返回一个替换次数 origin = "ale4 xc 19" data,counts = re.subn("\d+","KKK",origin) print(data,counts) # aleKKK xc KKK 2
compile()
compile()
regex = re.compile(r"\w*oo\w*") text = " JGood is ,he is cool" data = regex.findall(text) print(data) #['JGood', 'cool']
split()
split()
# split,根据正则匹配分割字符串 split(pattern, string, maxsplit=0, flags=0) # pattern: 正则模型 # string : 要匹配的字符串 # maxsplit:指定分割个数 # flags : 匹配模式 ***************************************** # 有分组情况下, 把分割的项也添加进去 origin = "hello alex bcd alex lge alex acd 19" r1 = re.split("(alex)", origin, 1) print(r1) # ['hello ', 'alex', ' bcd alex lge alex acd 19'] r2 = re.split("(al(ex))", origin, 1) print(r2) # ['hello ', 'alex', 'ex', ' bcd alex lge alex acd 19'] ***************************************** p = re.compile(r"\d+") test = p.split("one1two2three3four4") print(test) # ['one', 'two', 'three', 'four', ''] # 末尾有空字符串 => one,two2three3four4 => ['one'] two,three3four4 => .. test = re.split('[bc]','abcd') print(test) # ['a', '', 'd']
# 无分组 origin = "hello alex bcd alex lge alex acd 19" r = re.split("alex", origin, 1) print(r) # 有分组 origin = "hello alex bcd alex lge alex acd 19" r1 = re.split("(alex)", origin, 1) print(r1) r2 = re.split("(al(ex))", origin, 1) print(r2)
finditer()
finditer()
# 返回结果为迭代对象 p = re.compile(r"\d+") w = p.finditer(' 1 drum44ers druming , 11 ... 10 ...') for match in w: print(match.group(),match.span()) # 1 (1, 2) # 44 (7, 9) # 11 (23, 25) # 10 (30, 32)
反斜杠的困扰
与大多数编程语言相同,正则表达式里使用"\"
作为转义字符,这就可能造成反斜杠困扰。
假如你需要匹配文本中的字符"\"
,那么使用编程语言表示的正则表达式里将需要4个反斜杠"\\\\"
前两个和后两个分别用于在编程语言里转义成反斜杠,转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。
Python里的原生字符串很好地解决了这个问题,这个例子中的正则表达式可以使用r"\\"
表示。
同样,匹配一个数字的"\\d"
可以写成r"\d"
。有了原生字符串,你再也不用担心是不是漏写了反斜杠,写出来的表达式也更直观。
常用正则表达式
# IP: ^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$ # 手机号: ^1[3|4|5|8][0-9]\d{8}$ # 邮箱: [a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+
练习题:计算器
#!/usr/bin/env python # -*-coding:utf-8 -*- import re # 加减运算: def add_sub(origin): ''' 进行加减运算,按+ - 符号拆分, :param origin: 传入的表达式 :return: ''' data = re.split('([+-]{1})', origin) # 预处理 把 '' 转换成 '0' for i in range(len(data)): if data[i].strip() == '': data[i] = '0' # 预处理 负负得正 情况: for i in range(len(data)): if data[i].strip() == '0' and i != 0 : if data[i-1].strip() == '-': data[i+1] = '+' # 计算开始: total = 0 # flag = 0 默认加号 1为减号 flag = 0 for i in range(len(data)): if data[i].strip() == '-': flag = 1 continue elif data[i].strip() == '+': flag = 0 continue elif flag == 0 : total += float(data[i].strip()) if '.'in data[i] else int(data[i].strip()) elif flag == 1 : total -= float(data[i].strip()) if '.'in data[i] else int(data[i].strip()) return total # 计算乘除,乘方运算: def plus_div(origin): ''' 按前后顺序拆分,需要进行 乘除,乘方 运算的地方,获得式子,计算出结果,顶替回原来位置,循环此步骤,只剩加减法,再把只剩加减法的式子传入add_sub()计算 :param origin: 传入的表达式 :return: ''' while True: data = re.split('([-]?\d+\.?\d*\s*(?:[*/]|\*\*)\s*[-]?\d+\.?\d*)',origin,1) # 判断表达式是否只剩加减运算 , 传入 add_sub() 进行加减法的计算 if len(data) ==1: result = add_sub(data[0]) return result break # 判断表达式是否还包含 乘除,乘方 符号 ,如果包含,继续拆分运算,最终获得只剩加减的式子 elif len(data) ==3: counts = data[1] # 获得匹配出的 运算符号 * / 或 ** before, content, after = data else: print('Error') # 判断需要进行哪种运算, if '**' in counts: num = counts.split('**') left = float(num[0].strip()) if '.'in num[0] else int(num[0].strip()) right = float(num[1].strip()) if '.' in num[1] else int(num[1].strip()) total = left ** right elif '/' in counts: num = counts.split('/') left = float(num[0].strip()) if '.'in num[0] else int(num[0].strip()) right = float(num[1].strip()) if '.' in num[1] else int(num[1].strip()) total = left / right elif '*' in counts: num = counts.split('*') left = float(num[0].strip()) if '.'in num[0] else int(num[0].strip()) right = float(num[1].strip()) if '.' in num[1] else int(num[1].strip()) total = left * right # 此时只进行了表达式内的一次乘除,乘方运算, 计算结果拼接顶替原来的位置,继续循环直到只剩加减法 origin = before + str(total) + after def count(origin): """ 1.一步一步拆分括号,每获得一次 最里层的括号表达式 ,就传入plus_div()函数 进行 该表达式的所有乘除运算,再进行加减运算,返回该表达式最终结果, 拼接回原表达式,替换回括号位置.再循环寻找下一个括号内表达式,直到所有括号的表达式计算完毕,最终只剩一条只包含单纯的乘除加减运算的表达式. :param origin: 需要计算的表达式 :return: """ while True: # 拆分() 获得最里层的括号! data = re.split('\(([^()]+)\)',origin,1) if len(data) == 3: before,content,after = data result = plus_div(content) # () 里面的表达式传入 plus_div 进行计算 origin = before + str(result) + after # 把计算结果 拼接回原来括号的位置. 进行下一次括号寻找! else: # 当只剩一条只包含单纯的乘除加减运算的表达式时, result = plus_div(data[0]) return result break origin = " 6 * 2 + ( 4 -3.5 + 1 * 91 + (-1 +2) * 5 -5 ) / 2 + 2 ** 9" result = count(origin) print('表达式: ' + origin) print('我的计算结果 :\n' + str(result)) print('eval计算结果: \n'+ str(eval(origin)))