python3--re模块:正则表达式

发布时间:2018-05-08 21:15:24编辑:Run阅读(4995)

    怎么判断一个手机号码是否符合规范?

    根据手机号码一共11位并且只以13,14,15,18开头的数字这些特点,写了一段代码如下:

    while True:
        phone_number = input('please input your phone number : ')
        if len(phone_number) == 11 and phone_number.isdigit() and (phone_number.startswith('13') \
                or phone_number.startswith('14') or phone_number.startswith('15')\
                or phone_number.startswith('18')):
            print('是合法的手机号码')
        else:
            print('不是合法的手机号码')

    执行结果

    blob.png


    上面代码效果是可以实现,但是代码可读性差,正则怎么写呢?

    re模块实现

    import re
    phone_number = input('please input your phone number : ')
    if re.match('^(13|14|15|18)[0-9]{9}$', phone_number):
        print('是合法的手机号码')
    else:
        print('不是合法的手机号码')

    执行结果

    please input your phone number : 13971604811

    是合法的手机号码


    身份证验证是否合法?

    import re
    while True:
        ss = input('请输入你的身份证号: ').strip()
        if re.match('^[1-9]\d{14}(\d{2}[\dx])$', ss):
            print('身份证合法!')
            break
        else:
            print('不合法')

    执行结果

    blob.png


    从文件file中找出所有的手机号码--正则,文件中自行添加一些内容,加手机号码.

    import re
    
    with open('file', 'r')as f:
        l = []
        for i in f:
            ret = re.findall('1[3-9]\d{9}', i)
            l.extend(ret)
    print(l)

    执行结果

    ['13864814521', '13475695414']


    正则表达式

    字符组 : [字符组]

    在同一个位置可能出现的各种字符组成了一个字符组,在正则表达式中用[]表示

    字符分为很多类,比如数字、字母、标点等等。

    假如你现在要求一个位置"只能出现一个数字",那么这个位置上的字符只能是0、1、2...9这10个数之一

    正则

    待匹配字符

    匹配

    结果


    说明

    [0123456789]

    8

    True

    在一个字符组里枚举合法的所有字符,字符组里的任意一个字符

    和"待匹配字符"相同都视为可以匹配


    [0123456789]

    a

    False

    由于字符组中没有"a"字符,所以不能匹配

     

    [0-9]

     

    7

    True

    也可以用-表示范围,[0-9]就和[0123456789]是一个意思

     

    [a-z]

     

    s

     

    True

     

    同样的如果要匹配所有的小写字母,直接用[a-z]就可以表示

     

    [A-Z]

     

    B

     

    True

     

    [A-Z]就表示所有的大写字母

     

    [0-9a-fA-F]

     

    e

     

    True

     

    可以匹配数字,大小写形式的a~f,用来验证十六进制字符

    字符组:字符组代表一个字符位置上可以出现的所有

    范围: 根据asc码来的,范围必须是从小到大的指向,一个字符组中可以有对个范围


    字符:

     

    元字符

     

    匹配内容

    匹配除换行符以外的任意字符
    \w匹配字母或数字或下划线
    \s匹配任意的空白符
    \d匹配数字
    \n匹配一个换行符
    \t匹配一个制表符
    \b匹配一个单词的结尾
    ^匹配字符串的开始
    $匹配字符串的结尾
    \W

    匹配非字母或数字或下划线

    \D

    匹配非数字

    \S

    匹配非空白符

    a|b

    匹配字符a或字符b

    ()

    匹配括号内的表达式,也表示一个组

    [...]

    匹配字符组中的字符

    [^...]

    匹配除了字符组中字符的所有字符


    量词:

    量词

    用法说明

    *重复零次或更多次
    +重复一次或更多次
    ?重复零次或一次
    {n}重复n次
    {n,}重复n次或更多次
    {n,m}重复n到m次


    . ^ $

    正则待匹配字符匹配
    结果
    说明
    海.海燕海娇海东海燕海娇海东  匹配所有"海."的字符
    ^海.海燕海娇海东海燕只从开头匹配"海."
      海.$  海燕海娇海东海东只匹配结尾的"海.$"


    * + ? { }

    正则待匹配字符匹配
    结果
    说明
    李.?李杰和李莲英和李二棍子

    李杰
    李莲
    李二

     

    ?表示重复零次或一次,即只匹配"李"后面一个任意字符

     
    李.*李杰和李莲英和李二棍子李杰和李莲英和李二棍子

    *表示重复零次或多次,即匹配"李"后面0或多个任意字符

    李.+李杰和李莲英和李二棍子李杰和李莲英和李二棍子

    +表示重复一次或多次,即只匹配"李"后面1个或多个任意字符

    李.{1,2}李杰和李莲英和李二棍子

    李杰和
    李莲英
    李二棍

    {1,2}匹配1到2次任意字符


    注意:前面的*,+,?等都是贪婪匹配,也就是尽可能匹配,后面加?号使其变成惰性匹配

    正则待匹配字符匹配
    结果
    说明
    李.*?李杰和李莲英和李二棍子

    惰性匹配


    字符集[][^]

    正则待匹配字符匹配
    结果
    说明
    李[杰莲英二棍子]*李杰和李莲英和李二棍子

    李杰
    李莲英
    李二棍子

     

    表示匹配"李"字后面[杰莲英二棍子]的字符任意次

     
    李[^和]*李杰和李莲英和李二棍子

    李杰
    李莲英
    李二棍子

    表示匹配一个不是"和"的字符任意次

    [\d]456bdha3

    4
    5
    6
    3

    表示匹配任意一个数字,匹配到4个结果

    [\d]+456bdha3

    456
    3

    表示匹配任意个数字,匹配到2个结果


    分组 ()与 或 |[^]

     身份证号码是一个长度为15或18个字符的字符串,如果是15位则全部🈶️数字组成,首位不能为0;如果是18位,则前17位全部是数字,末位可能是数字或x,下面我们尝试用正则来表示:

    正则待匹配字符匹配
    结果
    说明
    ^[1-9]\d{13,16}[0-9x]$110101198001017032

    110101198001017032

       表示可以匹配一个正确的身份证号
    ^[1-9]\d{13,16}[0-9x]$1101011980010170

    1101011980010170

    表示也可以匹配这串数字,但这并不是一个正确的身份证号码,它是一个16位的数字

    ^[1-9]\d{14}(\d{2}[0-9x])?$1101011980010170

    False

    现在不会匹配错误的身份证号了

    ()表示分组,将\d{2}[0-9x]分成一组,就可以整体约束他们出现的次数为0-1次


    ^([1-9]\d{16}[0-9x]|[1-9]\d{14})$110105199812067023

    110105199812067023

    表示先匹配[1-9]\d{16}[0-9x]如果没有匹配上就匹配[1-9]\d{14}


    转义符 \

    在正则表达式中,有很多有特殊意义的是元字符,比如\d和\s等,如果要在正则中匹配正常的"\d"而不是"数字"就需要对"\"进行转义,变成'\\'。

    在python中,无论是正则表达式,还是待匹配的内容,都是以字符串的形式出现的,在字符串中\也有特殊的含义,本身还需要转义。所以如果匹配一次"\d",字符串中要写成'\\d',那么正则里就要写成"\\\\d",这样就太麻烦了。这个时候我们就用到了r'\d'这个概念,此时的正则是r'\\d'就可以了。


    正则待匹配字符匹配
    结果
    说明
    \d\d False

    因为在正则表达式中\是有特殊意义的字符,所以要匹配\d本身,用表达式\d无法匹配

    \\d\d True

    转义\之后变成\\,即可匹配

    "\\\\d"'\\d' True

    如果在python中,字符串中的'\'也需要转义,所以每一个字符串'\'又需要转义一次

    r'\\d'r'\d' True

    在字符串之前加r,让整个字符串不转义


    blob.png



    几个常用的非贪婪匹配

    *? 重复任意次,但尽可能少重复
    +? 重复1次或更多次,但尽可能少重复
    ?? 重复0次或1次,但尽可能少重复
    {n,m}? 重复n到m次,但尽可能少重复
    {n,}? 重复n次以上,但尽可能少重复


    .*?的用法

    . 是任意字符
    * 是取 0 至 无限长度
    ? 是非贪婪模式。
    何在一起就是 取尽量少的任意字符,一般不会这么单独写,他大多用在:
    .*?x
    
    就是取前面任意长度的字符,直到一个x出现


    re模块下的常用方法

    re.findall

    返回所有满足匹配条件的结果,放在列表里

    import re
    ret = re.findall('a', 'eva egon yuan')  # 返回所有满足匹配条件的结果,放在列表里
    print(ret)
    
    ret1 = re.findall('\d+', 'dfkhf4565fwef326wef315wef')  # 匹配一个数字1次或者多次
    print(ret1)
    
    ret2 = re.findall('\d', 'dfkhf4565fwef326wef315wef')  # 匹配一个数字(单个)
    print(ret2)

    执行结果

    ['a', 'a']

    ['4565', '326', '315']

    ['4', '5', '6', '5', '3', '2', '6', '3', '1', '5']


    re.search

    函数会在字符串内查找模式匹配,只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None

    import re
    ret = re.search('a', 'eva egon yuan')
    if ret:
        print(ret.group())  # 从结果对象中获取结果

    执行结果

    a


    为什么只有1个a呢?

    search和findall的区别:

    1 search找到一个就返回,findall是找所有

    2 findall是直接返回一个结果的列表,search返回一个结果的对象


    re.match

    import re
    ret = re.match('a', 'eva egon yuan')
    print(ret)

    执行结果

    None


    所有的match,意味着在正则表达式中添加了一个^,也就是'^a'的意思

    import re
    ret = re.match('e', 'eva egon yuan')
    if ret:
        print(ret.group())

    执行结果

    e


    match

    1 意味着在正则表达式中添加了一个^

    2 和search一样 匹配返回结果对象,没匹配到返回None

    3 和search一样 从结果中获取值 仍然用group()


    re.split

    先按'a'分割得到''和'bcd',在对''和'bcd'分别按'b'分割

    import re
    ret = re.split('[ab]', 'abcd')
    print(ret)

    结果

    ['', '', 'cd']


    re.sub

    将数字替换成'H',参数1表示只替换1个,如果不写1,则全部替换

    import re
    ret = re.sub('\d', 'H', 'esdf7sam9tom', 1)
    print(ret)
    ret = re.sub('\d', 'H', 'esdf7sam9tom')
    print(ret)

    执行结果

    esdfHsam9tom

    esdfHsamHtom


    re.subn

    将数字替换成'H',返回元组(替换的结果,替换了多少次)

    import re
    ret = re.subn('\d', 'H', 'esdf7sam9tom')
    print(ret)

    执行结果

    ('esdfHsamHtom', 2)


    re.compile

    将正则表达式编译成为一个正则表达式对象,规则要匹配的是3个数字

    正则表达式对象调用search,参数为待匹配的字符串

    编译 在多次执行同一条正则规则的时候才适用

    import re
    obj = re.compile('\d{3}')
    ret = obj.search('abc123eeshds')
    print(ret.group())
    # 可以执行多个方法
    ret1 = obj.match('999abc123e111ee888shds')
    ret2 = obj.findall('123e111ee888shds')
    print(ret1)
    print(ret2)

    执行结果

    123


    ['123', '111', '888']


    re.finditer

    finditer返回一个存放匹配结果的迭代器

    import re
    ret = re.finditer('\d', 'ds3sy4784a')   #finditer返回一个存放匹配结果的迭代器
    print(ret)  # print(next(ret).group())  #查看第一个结果
    print(next(ret).group())  #查看第二个结果
    print([i.group() for i in ret])  #查看剩余的左右结果

    执行结果


    3

    4

    ['7', '8', '4']


    re.findall的优先级查询

    import re
    ret = re.findall('www.(baidu|soso).com', 'www.soso.com')
    print(ret)
    # findall会优先把匹配结果组里内容返回,如果想要匹配结果,取消权限即可
    ret1 = re.findall('www\.(?:baidu|soso)\.com', 'www.baidu.com')
    print(ret1)

    执行结果

    ['soso']

    ['www.baidu.com']


    re.split的优先级查询

    import re
    ret = re.split('\d+', 'sdsff41fef5fe45')
    print(ret)
    
    # 在匹配部分上()之后所切出的结果是不同的
    # 没有()的没有保留所匹配的项,但是有()的却能够保留匹配的项
    # 这个在某些需要保留匹配部分的使用过程中是非常重要的
    ret1 = re.split('(\d+)', 'sdsff41fef5fe45')
    print(ret1)

    执行结果

    ['sdsff', 'fef', 'fe', '']

    ['sdsff', '41', 'fef', '5', 'fe', '45', '']


    匹配标签

    分组命名和search遇到分组

    有时候,不通过匹配周围的,无法匹配到想要的内容

    分组的意义

    1 对一组正则规则进行量词约束

    2 从一整条正则规则匹配的结果中优先显示组内的内容

    import re
    ret = re.search("<(?P\w+)>\w+", 'hello')
    print(ret.group())  # hello 因为h1和结果的h2不同,所以不对
    
    ret1 = re.search("<(?P\w+)>\w+", 'hello')
    print(ret1)  # 匹配不到了返回值为None
    
    ret2 = re.search("<(?P\w+)>\w+", 'hello')
    print(ret2.group())  # 匹配到了对应的内容

    执行结果

    blob.png


    练习题:

    计算下面例子的结果(用正则去匹配)

    s = '1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )'


    完整代码

    import re
    def cal(exp):
        """计算乘除的值,返回一个str(float)"""
        if '*' in exp:
            a, b = exp.split('*')
            return str(float(a) * float(b))
        elif '/' in exp:
            a, b = exp.split('/')
            return str(float(a) / float(b))
    
    def format(exp):
        """替换表达式中的多余符号"""
        exp = exp.replace('++', '+')
        exp = exp.replace('+-', '-')
        exp = exp.replace('-+', '-')
        exp = exp.replace('--', '+')
        return exp
    
    def dealwith(no_bracket_exp):
        # 匹配乘除法
        while True:
            mul_div = re.search('\d+(\.?\d+)?[*/]-?\d+(\.?\d+)?', no_bracket_exp)
            if mul_div:
                exp = mul_div.group()
                result = cal(exp)
                no_bracket_exp = no_bracket_exp.replace(exp, result, 1)
            else:break
        no_bracket_exp = format(no_bracket_exp)
        # 计算加减法
        lst = re.findall(r'[-+]?\d+(?:\.\d+)?', no_bracket_exp)
        res = str(sum(float(i) for i in lst))
        return res
    
    def remove_bracket(s):
        s = s.replace(' ', '')  # 替换空格为空
        while True:
            ret = re.search(r'\([^()]+\)', s)  # 匹配最内层的括号
            if ret:  # 能匹配到括号 就先处理括号内的加减乘除
                no_bracket_exp = ret.group()  # 拿到括号中的表达式
                ret = dealwith(no_bracket_exp)  # 把括号中的表达式交给的dealwith
                s = s.replace(no_bracket_exp, ret, 1)
            else:  # 不能匹配到括号 就字节处理加减乘除
                ret = dealwith(s)  # 把表达式交给的dealwith
                return ret
    
    
    s = '1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )'
    print(remove_bracket(s))

    执行结果

    2776672.6952380957

关键字