正则表达式 (一)

应用场景

  • 数据验证:检查字符串是否符合特定的格式,如电子邮件地址、电话号码、URL、IP 地址、日期格式等。
  • 数据抽取:从文本中提取所需的信息,如提取日志文件中的日期、错误代码或者提取网页中的链接。
  • 文本替换:在文本编辑器中进行查找和替换操作,或者在编程中对字符串进行复杂的替换,如大小写转换、删除多余空格等。
  • 文本分割:使用复杂的分隔符或模式对字符串进行分割,得到字符串数组。
  • 字符串解析:解析复杂格式的字符串,如解析自定义的配置文件内容。
  • 语法高亮:在编程开发环境中,对代码进行语法高亮显示。
  • 自然语言处理:在自然语言文本中识别句子、单词、短语的模式,如词干提取、词性标注等。
  • Web 爬虫:从网页源码中提取所需的数据,如图片链接、特定的文本信息等。
  • 文件重命名:在文件管理中,对大批量文件进行基于模式的重命名操作。
  • 日志分析:提取、分析服务器日志文件中的关键信息,以便进行故障排除、性能监控等。

使用原则

  • 简单化:尽可能使用简单的表达式。复杂的表达式可能难以理解和维护,并且可能导致性能问题。
  • 明确性:你的正则表达式应该明确无误地匹配你想要的文本,不应该匹配到错误的文本。
  • 避免过度使用:对于一些简单的字符串操作,如简单的查找、替换或分割,使用基本的字符串函数会更高效。
  • 避免贪婪匹配:默认情况下,正则表达式的量词(如 *+)是贪婪的,它们会尽可能多地匹配字符。在许多情况下,使用非贪婪量词(如 *?+?)可以提高效率并防止意外匹配。
  • 重用模式:编译常用的正则表达式,使用 re.compile() 函数。这样可以提升性能,因为编译后的模式可以重复使用。
  • 注释和文档化:复杂的正则表达式应该有注释说明每个部分的功能,使得其他开发者(或未来的你)可以理解它的工作原理。
  • 测试:对正则表达式进行彻底测试,确保它们在各种情况下都按预期工作,并处理好边缘情况。
  • 考虑可读性:尽管正则表达式可能非常强大,但它们也可以变得难以读懂。尽量写出易于理解的表达式,或将复杂的表达式分解成几个小的、管理得当的部分。
  • 避免过度匹配:确保正则表达式不会匹配到超出预期范围的文本。使用锚点(如 ^$)来限定匹配的开始和结束位置。
  • 性能考量:对于要在大量文本上执行的正则表达式,要考虑其性能影响。在可能的情况下,使用字符串的内建方法,如 startswith(), endswith(), find()in 运算符,因为它们通常比正则表达式更快。

基本使用

  • 说明

    • 正则是通过 re 模块提供支持的
    • 第三方模块 regex , 提供了与标准库 re 模块兼容的 API 接口,同时,还提供了更多功能和更全面的 Unicode 支持。
  • 相关函数

    • match

      • 从开头进行匹配,找到一个立即返回正则对象的结果,没有返回 None
        1
        2
        3
        4
        5
        # pyhton 依赖此模块完成正则功能
        import re

        # 从开头进行匹配,找到一个立即返回正则对象的结果,没有返回None
        m = re.match('abc', 'abchelloabc')
      • 可以使用 group (num) 或 groups () 匹配对象函数来获取匹配表达式
        匹配对象方法 描述
        group(num=0) 匹配的整个表达式的字符串,group () 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。
        groups() 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。
    • search

      • 匹配全部内容,任意位置,只要找到,立即返回正则结果对象,没有返回 None
      • re.matchre.search 的区别:
        re.match 只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回 None;而 re.search 匹配整个字符串,直到找到一个匹配。
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        # pyhton 依赖此模块完成正则功能
        import re

        # 匹配全部内容,任意位置,只要找到,立即返回正则结果对象,没有返回None
        m = re.search('abc', 'helloabc')

        if m:
        print('ok')
        # 获取匹配的内容
        print(m.group())
        # 获取匹配的位置
        print(m.span())
      • 可以使用 group (num) 或 groups () 匹配对象函数来获取匹配表达式
        匹配对象方法 描述
        group(num=0) 匹配的整个表达式的字符串,group () 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。
        groups() 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。
    • findall

      • 匹配所有内容,返回匹配的结果组成的列表,没有返回 None
      • 多个匹配模式,返回元组列表
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        import re

        # 匹配所有内容,返回匹配的结果组成的列表,没有返回None
        f = re.findall('abc', 'abcddgasabckjjghlabc')
        if f:
        print(f)

        # 多个匹配模式,返回元组列表
        result = re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10')
        print(result)
        # [('width', '20'), ('height', '10')]
    • compile

      • 根据字符串生成正则表达式的对象,拥有特定正则匹配,通过 match 、search、findall 匹配
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        import re

        # 根据字符串生成正则表达式的对象
        c = re.compile('abc')
        # 然后进行特定匹配
        # m = c.match('abcdefghigl')
        m = c.search('abcdefghigl')
        if m:
        print(m)

        print(c.findall('abcfdasdssabc'))

        将 re 模块中的 match 、search、findall 方法的处理过程分为了两步完成

  • 单个字符

    模式 描述
    普通字符 就是一对一的完全匹配
    . 匹配除了换行 ('\n') 的任意字符
    [] 匹配 [] 中间的任意一个字符
    [a-z] 匹配 a 到 z 的任意字符
    [0-9] 匹配 0 到 9 的任意字符
    [^abc] 匹配除 abc 之外的任意字符
    \d 匹配任意数字,等价于 [0-9]
    \D 匹配任意非数字字符,等价于 [^0-9]
    \w 匹配数字、字母、中文、下划线等 (就是字的意思)
    \W 匹配 \w 之外的所有字符
    \s 匹配任意空白字符,如:空格、\t、\n、\r、\f
    \S 匹配任意非空字符,即:\s 之外的所有字符
    \b 匹配一个单词边界,如:开头、结尾、标点、空格等
    \B 匹配非单词边界
  • 次数控制

    模式 描述
    * 前面的字符可以是任意次,正则的匹配默认是贪婪的
    + 前面的字符串至少是一次
    ? 匹配至多一次,0 次或 1 次
    {m} 匹配固定的 m 次
    {m,} 匹配至少 m 次
    {m,n} 匹配 m 到 n 次,在 m 和 n 之间尽量取多
    {m,n}? 匹配 m 到 n 次,在 m 和 n 之间尽量取少

    正则的匹配默认是贪婪的

  • 边界限定

    模式 描述
    ^ 以指定内容开头
    $ 以指定内容结尾
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import re

    # 以指定内容开头
    # c = re.compile(r'^abc')
    # 以指定内容结尾
    # c = re.compile(r'abc$')
    # 同时限制开头和结尾
    c = re.compile(r'^abc$')

    s = c.search('abc')

    if s:
    print('ok')
    print(s.group())
  • 分组匹配

    模式 描述
    a|b 匹配 a 或 b,具有最低的优先级
    (re) 表示一个整体,可以确定优先级;对正则表达式分组并记住匹配的文本
    (?:re) 类似 (re),但是只表示一个整体,不表示分组
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import re

    # | 表示或,具有最低的优先级
    # () 表示一个整体,可以确定优先级
    # () 还有分组匹配的作用,下次再讲
    # c = re.compile(r'ab|cd')
    # c = re.compile(r'a[bc]d')
    c = re.compile(r'a(hello|world)d')

    s = c.search('ahellod')

    if s:
    print('ok')
    print(s.group())
  • 正向先行断言

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import re

    s = """
    废%卢$刺&/蝟@猫@=菠+&萝#包#-小-说【t/-X<t+提……取<群%:=】7#-5=6%1%-3-3#7&2|6  “卡赞也一起去吧,号角峰是大冰川最高
    """

    if m := re.search(r'废.*?(?=\s)', s):
    print(m.group())

    • (?=\s) 是一个正则表达式的语法,被称为正向先行断言(Positive Lookahead)。在正则表达式中,先行断言用来指定一个位置,这个位置必须满足某个条件,但不包括在匹配结果中。
    • 具体来说:
      • (?=...) 是正向先行断言的结构,其中 ... 是你希望断言的部分。
      • \s 是一个特殊的字符类,匹配任何空白字符,包括空格、制表符、换行符等。

练习

  • 若匹配有特殊正则含义的字符怎么办,如:\d
    • 友情提醒:转移问题(python 中转义一次、正则解析转义一次)
  • 验证一个字符串是否是正确的邮箱格式
  • 验证一个字符串是否是正确的 url 格式

思路

  • 以功能为向导
  • 写出符合规则的若干字符串
  • 写一点测一点,不断调整
  • 最终完成功能