• Python正则(Re模块)


    正则

    一,简介/动机

    1.1 正则的出现动机

    ​ 我们在学习python的路途上,有许多需要操作文本数据的场景。无论是前后端/数据库都需要处理文本数据。处理文本时候,正则表达式给我们提供了强大的搜索,匹配,抽取,替换等功能。

    1.2 什么是正则表达式(regex

    ​ 正则表达式由一些字符和特殊符号组成的字符串,用来描述模式的重复或者表述多个字符。按照某种特定的模式匹配一系列有相似特征的字符串。python为我们提供了内置库re来支持正则表达式。

    1.3 搜索和匹配的比较

    • 模式匹配(pattern-matching
      • 按照表达式到字符串中搜索或者匹配符合模式的字符串或者部分字符串内容。
      • 模式匹配有两种方法:搜索,匹配
    • 搜索(searching)
      • 在字符串的任意部分搜索匹配的模式
      • 通过re中的search()方法实现
    • 匹配(matching)
      • 判断一个字符串能否从起始处或部分的匹配某个模式
      • 通过re中的match()方法实现

    1.4 第一个正则表达式

    import re
    
    my_str = 'abcaabccc'
    # 返回的嗾使re模块中的Match的实例对象
    res_search = re.search(r'abc', my_str)  # <re.Match object; span=(0, 3), match='abc'>
    res_match = re.match(r'abc', my_str)  # <re.Match object; span=(0, 3), match='abc'>
    
    • 匹配成功返回一个re模块中的Match类实例对象,如果失败则返回None。

    二,特殊符号和字符(元字符)

    ​ 上面我们只是使用固定的字符在匹配,这种功能查查某些字符是否在字符串中还可以,但是这并不是我们需要的功能。元字符可以帮助我们给模式加更多的花样。

    • 特殊符号. ^ * $
    • 特殊字符d w s 
    • 扩展表示法(?<name>...)

    2.1 择一匹配符号|

    • 符号:正则表达式1|正则表达式2
    • 别名:union,logicla or(逻辑或)
    • 用处:匹配表达式1,如果匹配不上尝试与表达式2匹配。
    regex_1 = r'abc'
    regex_2 = r'aa'
    regex_1 | regex_2 # 匹配的字符串 'abc'或者'aa'
    

    2.2 匹配任意单个字符

    • 符号:.
    • 限制:换行符 不包含在内
    • 如何推翻限制:改变编译标记[S或者DOTALL],该标记能够推翻这个限制。
    • 用处:需要匹配的字符是除换行符以外的任意字符是
    regex = '.'	# 匹配任意字符
    regex_1 = '..'	# 匹配任意两个字符 
    regex_2 = 'r.l'	# 匹配rxl x可以是任意字符
    

    2.3 单词边界匹配

    • 起始符号
      • 符号:^
      • 限制:限制以什么开始
    regex = '^a'	# 以a开头的字符
    
    • 结尾符号
      • 符号:$
      • 限制:限制以上面结尾
    regex = 'a$'	# 以a结尾的字符
    
    • 起始符和结尾符配合使用(严格限制)
    regex = "^baidu$"	# 限制字符为baidu
    
    • 边界限定符
      • 符号:
      • 限制:匹配字符的边界
      • 用处:类似与^,$
    regex = 'the'	# 匹配任何含有the的字符串
    regex_1 = 'the'	# 匹配以the开头的字符串
    regex_2 = 'the'	# 匹配以the结尾的字符串
    regex_2 = 'the'	# 匹配字符串the
    
    • 非单词边界符
      • 符号:B
      • 限制:包含但不是单词边界符
    regex = 'Bthe' # 包含the但是不是起始字符串
    

    2.4 创建字符集

    • 符号:[]
    • 作用:匹配一对方括号中包含的任意字符
    my_str = 'abc'
    regex = r'a[bd]c'	# 匹配包含abc 或者 adc 的字符串
    

    2.5 限定范围和否定

    ​ 匹配指定的字符范围:使用方括加上连字符-:例如[A-Z]表示所有大写字符或者[0-9]所有单个数值数字。如果在方括里加上一个脱字符^,表示不匹配给定字符集中的任何一个字符。

    regex = r'[a-z][R-T][0-9]'	# 三个字符,第一个为任意一个小写字母,第二个为大写的R|S|T,第三个为任意数字
    regex_1 = r'[^0-9]'	# 除数字以外的任意字符
    

    2.6 使用闭包操作符实现存在性和频数匹配

    ​ 前面我们都是在单个字符的进行匹配,我们可以通过闭包操作来控制它的频数。

    • 星号操作符
      • 符号:*
      • 作用:匹配其左侧的字符出现0次或者多次。
    regex = r'la*'	# ex: l la la la...a
    
    • 加号操作符
      • 符号:+
      • 作用:匹配一次或者多次(至少一次)
    regex = r'la+''	# ex: la laa la...a 
    
    • 问号操作符
      • 符号:?
      • 作用:匹配0次或者一次
    regex = r'la?'	# ex: l la
    
    • 大括号操作符
      • 符号:{}
      • 作用:控制其左侧的字符重复次数
      • 一个参数时:{N}匹配左侧表达式N
      • 两个参数时:{M,N}匹配左侧表达式M次~N
    regex = r'a{3}'	# aaa
    regex_1 = r'a{1,3}'	# 匹配字符a 至少1次 最多3次
    
    • 特别注意:紧跟着任何使用闭合操作符的后面,他将要求正则表达式尽可能少的次数匹配。这里涉及的知识点是贪婪匹配和非贪婪匹配

    2.7 表示字符集的特殊字符

    • 任意十进制数字
      • 符号:d
      • 相当于[0-9]
    • 全部字母数字的字符集
      • 符号:w
      • 相当于[a-zA-Z0-9]
    • 任何非十进制数字
      • 符号:D
      • 相当于[^0-9]
    • 非字母数字
      • 符号:W
      • 相当于[^a-zA-Z0-9]

    2.8 使用圆括号指定分组

    ​ 用圆括号包裹任何正则表达式,可以保存匹配成功的字符串内容。使用圆括号可以实现以下两个功能中任意一个或者两个。

    • 对正则表达式分组
    • 匹配子组
    • 符号:()
    my_str = '1993-08-22'	# 字符串出生的年月日
    regex = r'(d{4})-([0|1]d)-([0-3]d)'	# 匹配该字符串的正则表达式,使用了圆括号进行分组
    # 分组内容可以通过正则中的方法提取出来
    

    2.9 扩展方法

    • 符号:(?...)
    • 作用:
      • 前视匹配
      • 后视匹配
      • 条件检查
    (?:[a-zA-Z0-9]+.)	# (?:regex) 匹配不会保存下来供后续的使用和索引
    (?#aaa)	# (?#...) 此处不做匹配只做注释
    (?=.com)	# 如果一个字符串后面跟的是'.com'才做匹配
    (?!.com)	# 如果一个字符串不是后面跟'.com'才做匹配
    (?<800-)	# 如果字符串之前为'800-'才做匹配
    (?<!192)	# 如果字符串之前不为'192'才做匹配
    (?(1)y|x)	# 如果一个匹配组1存在,就与y匹配;否则,就与x匹配
    
    • 不加扩展的匹配结果
    my_str_1 = r'1993-08'
    regex_1 = r'([0-9]{4})-([0-9]{2})'
    res = re.match(regex_1, my_str_1)
    print(res.groups())  # ('1993', '08')
    
    • (?:)拓展
    regex_2 = r'(?:[0-9]{4})-([0-9]{2})'
    res = re.match(regex_2, my_str_1)
    print(res.groups())  # ('08',)
    
    • (?#)拓展
    regex_2 = r'([0-9]{4})-(?#[0-9]{2})'
    res = re.match(regex_2, my_str_1)
    print(res.groups())	# ('1993',)
    
    • (?=...)拓展
      • 此拓展需要在要应用的表达式后面
      • (表达式)(?=...)
    my_str_1 = 'www.baidu.com'
    my_str_2 = 'www.github.io'
    regex_1 = r'www.(.*).(.*)'
    regex_2 = r'www.(.*)(?=.com).(.*)'
    print(re.match(regex_1, my_str_1), re.match(regex_1, my_str_2))  # 都能匹配上
    print(re.match(regex_2, my_str_1), re.match(regex_2, my_str_2))  # 第二个没有匹配成功
    
    • (?<=...)拓展
      • 此拓展加在需要应用的分组或者单个表达式前面
      • (?<=...)(表达式)
    my_str_1 = '800-277'
    my_str_2 = '900-277'
    regex_1 = r'[0-9]{3,}-([0-9]*)'
    regex_2 = r'[0-9]{3,}-(?<=800-)([0-9]*)'
    print(re.match(regex_1, my_str_1).groups())  # ('277',)
    print(re.match(regex_2, my_str_1).groups())  # ('277',)
    print(re.match(regex_1, my_str_2).groups())  # ('277',)
    print(re.match(regex_2, my_str_2))  # None
    
    • (?(分组)表达式1|表达式2)
      • 如果分组存在就匹配表达式1
      • 如果分组不存在就匹配表达式2
    my_str_1 = '1abc2'
    my_str_2 = 'abcabc'
    regex = '(?P<name>[0-9]*)abc(?(name)[0-9]*|abc)'
    print(re.match(regex, my_str_1))
    print(re.match(regex, my_str_2).groups())
    

    三,正则表达式和python语言

    3.1 re模块:核心函数和方法

    两个概念

    • 正则表达式对象(regex object
    • 正则匹配对象(regex match object

    编译正则表达式(编译还是不编译?)

    • python代码是被先编译成字节码,然后在解释器执行。

    • 指定eval()或者exec()调用一个代码对象,性能会有明显的提升。

      • 使用预编译的代码对象比直接使用字符串要快,因为执行字符串形式的代码都必须把字符串编译成代码对象。
    • 正则预编译

      1. 模式匹配发生之前,正则表达式模式必须编译成正则表达式对象。
      2. 因为正则表达式经常会产生多次操作,所以预编译会提高性能。
      3. re模块中的re.compile()能够提供预编译功能。
      4. 模块函数会对已编译的对象进行缓存,所以不是所有的都需要预编译。
      5. purge()函数可以清除这些缓存。

    3.2 使用compile()函数编译正则表达式

    • 推荐预编译,但是并不是必须的,需要根据应用场景选择性的使用。
    • re模块的函数,几乎都可以作为正则对象的方法。(因为模块函数和方法的名字相同)
    • 对于一些特别的正则表达式编译,可选的标记可能以参数的形式给出。(例如允许忽略大小写等)
    • 这些标记也可以作为参数适用与大多数的re模块函数。

    3.3 匹配对象以及group()和groups()的方法

    • 匹配对象

      成功调用match或者search返回的对象,匹配对象主要有两个方法:group()和groups()

    • group

      要么返回整个对象,要么根据要求返回特定的子组。没有子组的要求,任然返回整个匹配。

    • groups

      仅仅返回一个唯一或者全部子组的元组。没有子组的要求,返回空元组。

    3.4 使用match()方法匹配字符串

    ###### 源码参考
    def match(pattern, string, flags=0):
        """尝试从字符串起始处匹配模式,成功返回一个匹配对象,失败返回None"""
        return _compile(pattern, flags).match(string)
    
    • 同时是re模块函数和正则表达式对象(regex object)方法
    • 从起始部份对模式进行匹配,匹配成功返回一个匹配对象,匹配失败返回None。
    • 匹配对象方法 group 可以用来显示那个成功的匹配。
    ##### 匹配成功时
    m = re.match('foo', 'foo')
    # m 是一个匹配对象
    if m is not None:
        print(m.group())  # foo
        
    ##### 匹配失败时
    m = re.match('foo', 'bar')
    # m 是一个匹配对象
    if m is not None:
        print(m.group())  # 
        
    ##### 匹配长字符串
    m = re.match('foo', 'food in table')
    if m is not None:
        print(m.group)	# foo
    

    3.5 使用search()在一个字符串中查找模式(搜索与匹配的对比)

    • 想要搜索的模式,出现在中间部分的概率比起始部分要大。
    • search与match工作方式完全一致。
    • 当起始部分不能匹配上的时候,match就会停止匹配。
    • search会严格的从左至右的搜索模式在字符串中第一次出现的位置。
    • 以上是使用的re模块函数!如果等价的表达式对象方法,可以选用参数posendpos指定目标字符串的搜索范围。
    ##### 源码参考
    def search(pattern, string, flags=0):
        """从左至右严格搜索 模式 在字符串中的匹配,匹配成功返回一个匹配对象,否则就返回None"""
        return _compile(pattern, flags).search(string)
    
    """
    对比match和search
    :match开头不匹配,停止匹配返回None
    """
    m = re.match('foo', 'seafood')
    if m is not None:
        print(m.group())
    else:
        print('没有搜索到')
    # 输出结果 没有搜索到
    
    m = re.search('foo', 'seafood')
    if m is not None:
        print(m.group())
    else:
        print('没有搜索到')
    # 输出结果 foo
    

    3.6 匹配多个字符串

    在写匹配模式的时候使用择一匹配符 |

    pattern = 'bat|blt|bnt'
    m = re.match(pattern, 'bat')
    if m is not None:
        print(m.group())
    else:
        print('没有匹配成功')
    # 匹配成功 返回 bat
    
    m = re.match(pattern, 'bzt')
    if m is not None:
        print(m.group())
    else:
        print('匹配失败')
    # 匹配失败
    

    3.7 匹配任何单个字符

    • . 号不能匹配换行符 和空字符串
    pattern = '.end'
    m = re.match(pattern, 'bend')
    if m is not None:
        print(m.group())
    else:
        print('匹配失败')
    # 匹配成功 返回bend
    
    pattern = '.end'
    m = re.match(pattern, 'end')
    if m is not None:
        print(m.group())
    else:
        print('匹配失败')
    # . 号不匹配空字符串 匹配失败
    
    pattern = '.end'
    m = re.match(pattern, '
    end')
    if m is not None:
        print(m.group())
    else:
        print('匹配失败')
    # . 号不匹配换行符,匹配失败
    
    pattern = '.end'
    m = re.search(pattern, 'in the end.')
    if m is not None:
        print(m.group())
    else:
        print('匹配失败')
    # 匹配成功 返回‘ end’
    
    • 如果需要匹配句点可以使用反斜杠进行转义
    pattern = '.end'
    m = re.search(pattern, 'in the .end')
    if m is not None:
        print(m.group())
    else:
        print('匹配失败')
    # 匹配成功 返回‘.end’
    

    3.8 字符集 [] 与择一选择符 |

    • 字符集表示匹配字符集中的任意字符集即可,而择一符是表示选择一边的模式进行匹配
    • 相对来说择一选择符更加严格

    3.9 重复,特殊字符以及分组

    • 正则表达式常见的情况
      • 特殊字符的使用
      • 正则表达式模式的重复出现
      • 使用圆括号对匹配模式的各部分进行分组和提取操作
    • 我们使用正则模拟提取html中数据
    <p>电影名称:<span>杀破狼</span>演员:<span>吴京</span></p>
    
    import re
    
    my_str = '<p>电影名称:<span>杀破狼</span>演员:<span>吴京</span></p>'
    pattern = '<p>电影名称:<span>(.*)</span>演员:<span>(.*)</span></p>'
    m = re.match(pattern, my_str)
    if m is not None:
        print(m.groups())   # 返回一个元组 ('杀破狼', '吴京')
        print(m.group())	# group 返回的是一个匹配对象 
        print(m.group(1))  # 拿到第一个分组的内容 杀破狼
        print(m.group(2))   # 拿到第二个分组的内容 吴京
    else:
        print('没有匹配到!')
    
    • group 和 groups 比较
      • group 通常用于以普通形式显示所有匹配部分,即返回一个匹配对象。
      • 而 groups 方法来获取一个包含所有匹配字符串的元组

    3.10 匹配字符串的起始和结尾以及单词边界

    • 回顾起始符以及单词边界
      • ^ 起始符
      • $ 结尾符
      •  边界
      • B 非边界
    # 起始符 匹配成功过返回 the
    m = re.search('^the', 'the end')
    if m is not None:
        print(m.group())
    else:
        print('匹配失败')
    
    # 结尾符 匹配失败 字符串不是以 the 结尾
    m = re.search('the$', 'the end')
    if m is not None:
        print(m.group())
    else:
        print('匹配失败')
    
    # 边界符  匹配成功 返回end
    m = re.search(r'end', 'the end time')
    if m is not None:
        print(m.group())
    else:
        print('匹配失败')
    
    # 非边界符 B 匹配失败 字符串中的end是单词边界
    m = re.search(r'Bend', 'the end time')
    if m is not None:
        print(m.group())
    else:
        print('匹配失败')
    
    • 注意:这里使用了原始字符串 r'end'才导致模式匹配没有歧义,如果忘记使用原始字符串,匹配结果可能会和预期不符合。

    3.11 使用findall()finditer()查找每一次出现的位置

    • findall()
      • 查询某个表达式模式全部的非重复出现情况。
      • 总是返回一个列表。
        • 成功时,返回包含所有匹配成功的匹配部分(从左向右按出现顺序排列)
        • 失败时,返回一个空列表
    print(re.findall('car', 'car'))  # ['car']
    print(re.findall('car', 'carry me by the car'))  # ['car', 'car']
    print(re.findall('car', 'no case'))  # []
    
    • findall() 搭配子组使用
      • 子组允许从单个正则表达式中抽取特定模式的一种机制。
      • 一个模式匹配成功一次返回的是一个列表的元素,对于多个匹配成功的,组成元组称为列表的一个元素。
    my_str = '<p>电影名称:<span>杀破狼</span>演员:<span>吴京</span></p>'
    pattern = '<p>电影名称:<span>(.*)</span>演员:<span>(.*)</span></p>'
    
    print(re.findall(pattern, my_str))  # [('杀破狼', '吴京')]
    
    • finditer()
      • findall() 类似,但是更节约内存的变体。
      • 在匹配对象中迭代,返回一个迭代器。
    s = 'this and that.'
    pattern = r'(thw+) and (thw+)'
    
    re.findall(pattern, s)  # [('this', 'that')]
    re.finditer(pattern, s) # <callable_iterator object at 0x0000023D17765160>
    next(re.finditer(pattern, s)).group(1)  # this
    new_list = [g.groups() for g in re.finditer(pattern, s)]    # [('this', 'that')]
    
    • 注意事项
      • 两者匹配输出一致,只是 finditer() 返回的是一个可迭代对象
      • 如果等价的表达式对象方法,可以选用参数posendpos指定目标字符串的搜索范围。

    3.12 使用 sub()subn() 搜索与替换

    • 两者几乎一样,都是将模式匹配成功部分的字符串进行替换。
    • 用来替换的部分通常是一个字符串,或者返回一个字符串的函数。
    • subn() 还返回一个表示替换的总数,返回一个元组 (替换后的字符串, 替换总数)
    • sub|subn(pattern, repl, string, count=0, flags=0)
    """
    :pattern 正则表达式
    :repl 替换的部分
    :string 需要被替换的字符串
    """
    
    pattern = '[0-9]'
    rpl = 'x'
    my_str = '我的电话是15502732718'
    
    res = re.sub(pattern, rpl, my_str)
    print(res)  # 我的电话是xxxxxxxxxxx
    
    res = re.subn(pattern, rpl, my_str)
    print(res)  # ('我的电话是xxxxxxxxxxx', 11)
    
    • 使用匹配对象的group()方法除了能够取出匹配分组编号外,还可以使用N其中N是在替换字符串中使用的分组编号
    # 将 20/06/2020 改写成 2020/06/20
    my_str = '20/06/2020'
    pattern = r'(d{2})/(d{2})/(d{4})'
    rpl = r'3/2/1'
    
    res = re.sub(pattern, rpl, my_str)
    print(res)	# 2020/06/20
    

    3.13 在限定模式上使用 split() 分隔字符串

    • 与字符串分隔工作方式类似
    • 基于正则表达式的模式分隔字符串,更加强大。
    • 可以使用 MAX 参数设定一个值指定最大分割数。
    split(pattern, string, maxsplit=0, flags=0):
        """
    	根据出现的模式拆分字符串,返回包含结果字符串的列表。在模式中使用()捕获子组,捕获的子组也会作为结果的一部分返回,如果限制字符串次数,则会按照指定次数分割,其余部分作为最后一个元素返回。
    	:pattern 模式(正则表达式)
    	:string 需要分割的字符串
    	:maxsplit 最大分割次数
    	"""
    
    my_str = 'str1:str2:str3'
    patterns = ':'
    
    re.split(patterns, my_str)  # ['str1', 'str2', 'str3']
    re.split(patterns, my_str, 1) # ['str1', 'str2:str3']
    
    my_str = 'str1:str2:str3'
    patterns = '(:)'
    print(re.split(patterns, my_str))  # ['str1', ':', 'str2', ':', 'str3']
    print(re.split(patterns, my_str, 1))  # ['str1', ':', 'str2:str3']
    

    3.14 拓展符号

    • (?i)忽略大小写, re.I
    my_str = 'yes, Yes! YES?'
    patterns = r'(?i)yes'
    
    re.findall(patterns, my_str)  # ['yes', 'Yes', 'YES']
    
    • (?m)多行匹配,而不是将整个字符串视为单个实体。 re.M
    my_str ="""
    this is a pig,
    that why we shout!
    there is nothing!
    """
    patterns = '(?m)^th.*'
    
    re.findall(patterns, my_str)
    # ['this is a pig,', 'that why we shout!', 'there is nothing!']
    
    • (?s) 该标记表明 . 号能够表示 符号。 re.S
    my_str = """
    this is first line
    this is second line
    this is third line
    """
    patterns = 'th.+'
    
    re.findall(patterns, my_str)
    # ['this is first line', 'this is second line', 'this is third line']
    
    my_str = """
    this is first line
    this is second line
    this is third line
    """
    patterns = '(?s)th.+'
    
    print(re.findall(patterns, my_str))
    # ['this is first line
    this is second line
    this is third line
    ']
    
    • (?x) 允许用户通过抑制在正则表达式中使用空白符(除了在字符类中或者在反斜杠转义中)来创建易读的正则表达式。

      例如匹配 (800) 555-1212 想提取中的三组数值

      my_str = '(800) 555-1212'
      patterns = r"""(?x)
      ((d{3})) # 800
      [ ]         # 空格
      (d{3})     # 555
      -           # -
      (d{4})     # 1212
      """
      print(re.search(patterns, my_str).groups())
      # ('800', '555', '1212')
      
    • (?P<name>)(?P=name)

      前者通过名称标识符而不是使用从1到N的增量数字保存匹配,不使用名称标识符,则通过数字来检索。

      通过 g<name> 取出对应的值

    my_str = '800 277-1212'
    pattern = r'(?P<区号>d{3}) (?P<前缀>d{3})-(?:d{4})'
    rpl = '(g<区号>) g<前缀>-xxxx'
    
    re.sub(pattern, rpl, my_str)	# (800) 277-xxxx
    

    ​ 后者可以在一个相同的正则表达式中重用模式。

    在测试中目前为止都是失败的
    
  • 相关阅读:
    39岁了,我依旧要谈梦想
    ASP原码加密工具介绍
    extjs_07_combobox&amp;tree&amp;chart
    JS0基础学习笔记(1)
    AndroidUI组件之ActionBar
    Sourcetree 更新git账号密码
    iOS人脸识别核心代码(备用)
    ios 中的tintColor
    appStore上传苹果应用程序软件发布
    iOS9适配 之 关于info.plist 第三方登录 添加URL Schemes白名单
  • 原文地址:https://www.cnblogs.com/raoqinglong/p/13232999.html
Copyright © 2020-2023  润新知