Python 基础之正则之二 匹配分组,正则相关函数及表达式修饰符

时间:2022-03-26 08:54:53

四.匹配分组  

[元字符] 分组符号

a|b   匹配字符a 或 字符b  (如果两个当中有重合部分,把更长的那个放前面)

(ab)   匹配括号内的表达式 ,将()作为一个分组

num  引用分组num匹配到的字符串

(?P<name>)给分组命名

(?P=name)引用别名: 把name分组所匹配到的内容引用到这里

1.正常分组 ()

## (1) 正常情况下用()圆括号进行分组 可以用\1 反向引用第一个圆括号匹配的内容。

## (2) (?:正则表达式) 表示取消优先显示的功能

(正则表达式)   把分组的匹配到的内容加到列表,不是分组的匹配匹配到的内容不加到列表中
(?:正则表达式) 把分组的匹配到的内容和没有分组匹配到的内容一起加到列表里

print(re.findall('.*?_good', 'one_good two_good three_good'))
# 注意从头开始的,然后进行匹配,匹配搭配_good开始不是任意字符
# ['one_good', ' two_good', ' three_good']

print(re.findall('(.*?)_good', 'one_good two_good three_good'))
# ['one', ' two', ' three']
# 在匹配的时候是需要_good才能符合匹配条件的,但是并没有加到列表中
# 只把分组匹配搭配的内容加入到列表中

print(re.findall('(?:.*?)_good', 'one_good two_good three_good'))
# 将匹配到的内容全部加到列表中,不管是分组内的还是分组外的
# ['one_good', ' two_good', ' three_good']

## (3)| 代表或
# 既想匹配abc 还要匹配 abcd
lst = re.findall("abc|abcd", "abc234abcd234390dbohdq")
print(lst)
# 有缺陷,不能够都匹配到
# ['abc', 'abc']
lst = re.findall("abcd|abc", "abc234abcd234390dbohdq")
print(lst)
# ['abc', 'abcd']

#对.匹配进行解析
\让原本有意义的字符变得无意义
.代表任意字符,除了\n
如果想要让正则匹配有个
\.让点的转义失效
# 匹配小数

# 231.321 3213 312 89.32 0.3231
lst = re.findall("\d+\.\d+", "231.321 3213 312 89.32 0.3231")
# \d+ 表示匹配一个或者多个数字
print(lst)
# ['231.321', '89.32', '0.3231']

# 使用分组的形式来匹配小数和正数
# 不正确版本,加入的是小数部分或空字符
lst = re.findall("\d+(\.\d+)?", "231.321 3213 312 89.32 0.3231")
print(lst)
# ['.321', '', '', '.32', '.3231']
# 正确版
lst = re.findall("\d+(?:\.\d+)?", "231.321 3213 312 89.32 0.3231")
print(lst)
# ['231.321', '3213', '312', '89.32', '0.3231']

# 从字符串中匹配135或171的手机号
print("=========================135")
lst = re.findall("(?:135|171)\d{8}", "135678987654w171skdjfksjkf 11312312317178787887")
print(lst)

lst = re.findall("135\d{8}|171\d{8}", "135678987654w171skdjfksjkf 11312312317178787887")
print(lst)

lst = re.findall("(?:135|171)[0-9]{8}", "135678987654w171skdjfksjkf 11312312317178787887")
print(lst)
# 结果都是:
# ['13567898765', '17178787887']

2.命名分组

## (3) (?P<组名>正则表达式) 给这个组起一个名字
## (4) (?P=组名) 引用之前组的名字,把该组名匹配到的内容放到当前位置
import re
#首先是正常分组对这些内容的匹配及结果
strvar = '<h1>sdfsdfsdfsdf</h1>'
lst = re.findall("<(.*?)>(.*?)<(.*?)>",strvar)
print(lst)
# [('h1', 'sdfsdfsdfsdf', '/h1')]
strvar = "123<h1>sdfsdfsdfsdf</h1>  123<h2>ppoo</h2>"
lst = re.findall("<(.*?)>(.*?)<(.*?)>",strvar)
print(lst)
# [('h1', 'sdfsdfsdfsdf', '/h1'), ('h2', 'ppoo', '/h2')]

#反向引用,
# 拿到已经匹配到的值,再引用一次
#引用第一个口号里面的内容 \1
#引用第二个括号里面的内容 \2 依次类推
#例:
strvar = "123<h1>sdfsdfsdfsdf</h1>  123<h2>ppoo</h2>"
lst = re.findall(r"<(.*?)>(.*?)</\1>",strvar)
print(lst)
#findall能根据反向匹配,但是不显示在匹配后的数据里
# [('h1', 'sdfsdfsdfsdf'), ('h2', 'ppoo')]

#命名分组方式1:
strvar = 'd3j5sdj'
obj = re.search(r"(.*?)\d(.*?)\d(.*?)\1\2",strvar)
print(obj)
# <_sre.SRE_Match object; span=(0, 7), match='d3j5sdj'>
res = obj.group()  #对象.方法
print(res)
#search反向引用的数据也匹配到数据中,关于search说明请见正则相关函数
# 结果为: d3j5sdj

#命名分组方式2:
#用命名分组进行反向引用
obj = re.search("(?P<tag1>.*?)\d(?P<tag2>.*?)\d(?P<tag3>.*?)(?P=tag1)(?P=tag2)",strvar)

print(obj)
# 得到的是一个对象: <_sre.SRE_Match object; span=(0, 7), match='d3j5sdj'>
res = obj.group()
# 使用对象.方法 的到数据: d3j5sdj
print(res)

#命名分组方式3:
obj = re.search(r"(?P<tag1>.*?)\d(?P<tag2>.*?)\d(?P<tag3>.*?)\1\2",strvar)
print(obj)
# <_sre.SRE_Match object; span=(0, 7), match='d3j5sdj'>
res = obj.group()
print(res)
# 使用对象.方法 的到数据: d3j5sdj

五.正则相关函数

findall  匹配字符串中相应内容,返回列表 [用法: findall("正则表达式","要匹配的字符串")]

search   通过正则匹配出第一个对象返回,通过group取出对象中的值

match    验证用户输入内容

split    切割

sub      替换

finditer 匹配字符串中相应内容,返回迭代器

compile  指定一个统一的匹配规则

1.findall

上面已经讲过不多做讲解

2.search 

通过正则匹配出第一个对象返回,通过group取出对象中的值

Search 通过正则匹配出第一个对象返回(只匹配一次),通过group取出对象中的值

findall 把满足条件的所有值都找出来放到列表里面返回
search  找到一个满足条件的值就直接返回,扔到一个对象当中
想要获取对象中的值,group 对象.group()
# 注意只匹配一次
obj = re.search("\d+", "dohd3093dhdoqhd9023")
# \d+ 表示匹配多次数字,到不符合停止,search只匹配一次所有是3093
print(obj)  # 返回的是一个对象 #<_sre.SRE_Match object; span=(4, 8), match='3093'>
res = obj.group()
print(res)  # 3093

# 匹配www.baidu.com 或者 www.sohu.com
obj = re.search("(w{3})\.(baidu|sohu)\.(com)", "www.baidu.com www.sohu.com")
res = obj.group()
#虽然两个地址都符合条件,但是search只匹配一次
print(res)
#结果: www.baidu.com

obj = re.search("(www)\.(baidu|sohu)\.(com)", "www.sohu.com")
res = obj.group()
print(res) # www.sohu.com
# 数字1 代表第一个括号里面的内容
res = obj.group(1)
print(res)  # www
# 数字2 代表第二个括号里面的内容
res = obj.group(2)
print(res)  # sohu
# 数字2 代表第三个括号里面的内容
res = obj.group(3)
print(res) # com

# groups 一次性把所有小括号里面的内容匹配出来
res = obj.groups()
print(res) # ('www', 'sohu', 'com')

3.match验证用户输入内容

# search 和 match 用法一样,区别在于match 在正则的前面加了一个^条件 [必须以...开头]
# search 只需要在正则的前面加上^ 就可以取代 match
strvar = "uuyydfopopopop3434sdf234"
# obj = re.search("^d.*?",strvar)  #等价于,match
obj = re.search("d.*?\d", strvar)
res = obj.group()
print(res) # dfopopopop3

# match
obj = re.match("d.*?\d", strvar)
print(obj)  # None 必须以d作为字符串的开头,所有匹配不到返回None
# res = obj.group()
# print(res)

4.split 切割

#例1:
#字符串的方法是将,替换成| 再用字符串切割用| 切割为列表
strvar = "one|two|three,four"
lst = strvar.replace(",","|").split("|")
print(lst)
#['one', 'two', 'three', 'four']

#使用re的切割方法
strvar = "one,two|three%four"
res = re.split(r"[,|%]",strvar)
#通过多个字符选一个匹配切割最后得到列表
print(res)
#['one', 'two', 'three', 'four']

#例2:
strvar = "one234234two2three909090four"
res = re.split(r"\d+",strvar)
# 通过用\d+ 为分界来切割,将得到的数据返回放入列表
print(res)
# ['one', 'two', 'three', 'four']

5.sub 替换

#格式: sub(pattern, repl, string, count=0, flags=0)
#例:

strvar = "one,two|three%four"
res = re.sub("[,|%]","-",strvar)
print(res)
# one-two-three-four

#后面可以选择替换的次数
#只替换一次
res = re.sub("[,|%]","-",strvar,1)
print(res)
# one-two|three%four
res = re.sub("[,|%]","-",strvar,count=1)
print(res)
# one-two|three%four
#subn 和 sub 用法一样,最大的区别在返回值,返回一个元组,包含替换的次数
res = re.subn(r"[,|%]","-",strvar)
print(res)
# ('one-two-three-four', 3)

6.finditer 匹配字符串中相应的内容,返回迭代器

# finditer 和 findall 用法一样,区别在于返回的是迭代器
strvar = "sdfsdff&*&*%^%234sdfsdfskdjfk3sdf23"
#例:使用findall

lst = re.findall("\d", strvar)
print(lst) # ['2', '3', '4', '3', '2', '3']
#例:使用finditer
res = re.finditer("\d", strvar)
print(res) #<callable_iterator object at 0x0000021398B5ACF8>
from collections import Iterator
print(isinstance(res, Iterator))  # True
it = re.finditer("\d", strvar)
#lst = list(it)
#print(lst)
obj = next(it)
res = obj.group()
print(res)
# 2
for i in it:
    res = i.group()
    print(res)
# 输出为: 3 4 3 2 3

7.compile  指定一个统一的匹配规则

写一套正则,程序就需要重新编译一次
同一个正则多处使用,反复编译会浪费时间
这样的话,就可以使用compile来定义,终身受益
#例:
rule = re.compile("\d+") #规则是匹配至少一次数字
print(rule) # re.compile('\\d+')

strvar = "sdfsdfs234kjkjk*(*9343dsf3"
# 用search 匹配至少一次数字贪婪算法知道不符合才停止
obj = rule.search(strvar)
# 获取一个对象
print(obj) # <_sre.SRE_Match object; span=(7, 10), match='234'>
# 对象.方法得到数据
print(obj.group()) # 234

#使用规则用findall进行匹配
lst = rule.findall(strvar)
# ['234', '9343', '3']
print(lst)

六.正则表达式修饰符

常用修饰符说明:

re.I使匹配对大小写不敏感

re.M     多行匹配,影响 ^ 和 $

re.S使 . 匹配包括换行在内的所有字符

#例1:

strvar =  """<h1>sdfsf</H1>
<H1>dd22</h1>
<h1>aabb</H1>"""
# 不受大小写影响,和不受换行影响结合
lst = re.findall("^<h1>(.*?)</h1>$",strvar,flags=re.M|re.I)
print(lst) # ['sdfsf', 'dd22', 'aabb']
#例2:
strvar = """<h1>sdfsf</h1>
<h1>dd22</h1>
<h1>aabb</h1>"""
#不受多行匹配影响
rule = re.compile("^<h1>(.*?)</h1>$",flags=re.M)
#rule = re.compile("^<h1>(.*?)</h1>$",flags=re.M|re.I)
lst = rule.findall(strvar)
print(lst)
print("===========")
#re.S 使.匹配包括换行在内的所有字符
obj = rule.search(strvar)
print(obj.group())

正则相关练习

import re

# 1、匹配整数或者小数(包括正数和负数)
lst = re.findall(r"[+-]*?\d+\.*?\d*?",'-555555 w3213 +321 321.321 321.321')
print(lst)

# 2、匹配年月日日期 格式 2018-12-31
lst = re.findall( '\d{4}-(?:0[0-9])-(?:[0-2]\d)|\d{4}-(?:0[0-9])-

(?:[3][0-1])|\d{4}-(?:1[0-2])-(?:[0-2]\d)|\d{4}-(?:1[0-2])-(?:[3][0-1])','2018-12-31 2018-09-02 2018-02-29 2018-10-31 qwhowq3056-13-24 2018-01-32')
print(lst)

# 3、匹配qq号 5-12 首字符没有0

import re
qqnum = input("请输入您的qq号:")
lst = re.findall('^[1-9]\d{4,11}$',qqnum)
if len(lst) == 0:
    print("您输入的qq号格式有错误")
else:
    print("您的qq号为:",lst[0])

# 4、11位的电话号码
import re
call_num = input("请输入您的电话号码:")
lst = re.findall("^[1-9]\d{10}$",call_num)
if len(lst) == 0:
    print("您输入的手机号有误")
else:
    print("您输入的电话号码是:",lst[0])

# 5、长度为8-10位的用户密码 : 包含数字字母下划线
import re
lst = re.findall('[\da-zA-Z_]{8,10}','js_28aAA')
print(lst)

# 6 写出4位验证码的正则匹配

import re
lst = re.findall('[\da-zA-Z]{4}','fF4n 1231 312313  dads12   123 d a ddd')
print(lst)

# 7、匹配邮箱地址 邮箱规则
# @之前必须有内容且只能是字母(大小写)、数字、下划线(_)、减号(-)、点(.)
# @和最后一个点(.)之间必须有内容且只能是字母(大小写)、数字、点(.)、减号(-),且两个点不能挨着
# 最后一个点(.)之后必须有内容且内容只能是字母(大小写)、数字且长度为大于等于2个字节,小于等于6个字节
import re
lst = re.findall(r'[a-zA-Z\d\-\_\.]+@[a-zA-Z\d\.\-]+\.[a-zA-Z]{2,6}','1.9_05697@q.3q.com 1.9_05697@q.3q.com1234 9.j05697@q-.q.com6789')
print(lst)

import re
# 8、从类似
# <a>wahaha</a>
# <b>banana</b>
# <h1>qqxing</h1>
# <h1>q</h1>
# 这样的字符串中,
# 1)匹配出 wahaha,banana,qqxing 内容。
"<h1>(.*?)<h1>"
"""
import re
strvar = "<h1>qqxing</h1> <b>banana</b>"
lst = re.findall("<.*?>(.*?)<.*?>",strvar)
print(lst)
re.search("<.*?>(.*?)<.*?>",strvar)
"""
import re
strvar= """<a>wahaha</a> <b>banana</b> <h1>qqxing</h1> <h1>q</h1>
"""
lst1 = re.findall(r"<.*?>([^<>]{2,})<.*?>",strvar)
print(lst1)

# 2)匹配出 a,b,h1这样的内容
lst2 = re.findall(r"</(.*?)>",strvar)
print(lst2)
lst3 = re.findall(r"<([^/]*?)>",strvar)
print(lst3)

# 9.

print("========把字符串进行运算==========")
strvar = "5*6-7/3"
def suan(strvar):
    strvar = strvar
    lst = re.findall("(?:\d+)(?:[*/+-])(?:\d+)", strvar)
    for i in lst :
        obj = re.search("(\d+)([*/+-])(\d+)",i)
        #res = obj.group()
        #print(res)
        if obj.group(2) == "*":
            res = int(obj.group(1)) * int(obj.group(3))
            #print(res)
        elif obj.group(2) == "/":
            res = int(obj.group(1)) / int(obj.group(3))
            #print(res)
        elif obj.group() == "+":
            res = int(obj.group(1)) + int(obj.group(3))
            #print(res)
        elif obj.group(2) == "-":
            res = int(obj.group(1)) - int(obj.group(3))
            #print(res)
        strvar = strvar.replace(i,str(res))
    return strvar
strvar = suan(strvar)
print("=====1========")
print(strvar)
strvar = suan(strvar)
print("=====2========")
print(strvar)

# 10.计算器

import re
#计算乘除的方法
def parse_exp(exp):
    if "*" in exp:
        a,b = exp.split("*")
        #print(a,b)
        return str(float(a) * float(b))
    if "/" in exp:
        a,b = exp.split("/")
        return str(float(a) / float(b))
#去除++ +- -- -+  bug 情况
def exp_format(exp):
    #如果出现下面情况就替换相应的
    exp = exp.replace("+-","-")
    exp = exp.replace("--","+")
    exp = exp.replace("-+","-")
    exp = exp.replace("++","+")
    return exp
#实际计算
def exp_calc(strvar):
    #计算乘除
    while True:
        res_obj = re.search("\d+(\.\d+)?[*/][+-]?\d+(\.\d+)?",strvar)
        if res_obj:
            res = res_obj.group()
            #print(res) #5*-2
            res2 = parse_exp(res)
            #print(res)
            strvar = strvar.replace(res,res2)
        else:
            break
    #print(strvar)
    #计算加减
    res = exp_format(strvar)
    #print(lst)
    lst = re.findall("[+-]?\d+(?:\.\d+)?",res)
    #print(lst)
    count = 0
    for i in lst:
        count += float(i)
    #print(count)
    return count
#去除括号
def remove_bracket(strvar):
    while True:
        #匹配出最里面一层的括号
        res_obj = re.search("\([^()]+\)",strvar)
        if res_obj:
            res_exp = res_obj.group()
            #print(res_exp)
            #计算口号里面的值,exp_calc
            res = str(exp_calc(res_exp))
            #print(res,type(res))
            #把算好的实际数值转换成字符串,替换以前的圆括号
            strvar = strvar.replace(res_exp,res)
        else:
            #直接返回替换好的字符串
            return strvar
#主函数
def main(strvar):
    #先把所有的空格去掉
    strvar = strvar.replace(" ","")
    #去掉空格
    res = remove_bracket(strvar)
    #print(res)
    #计算最后结果
    return exp_calc(res)
a = '1-2*((60-30+(-40/5)*(9-2*5/3+7/3*99/4*2998+10*568/14))-(-4*3)/(16-3*2))'
res = main(a)
print(res)
#验证结果
res = eval(a)
print(res)