Python中的正则表达式完全指南

Python中的正则表达式完全指南

正则表达式(Regular Expressions,简称regex)是一个非常强大的工具,广泛应用于文本处理、数据清洗、日志分析等领域。Python 提供了 re 模块来处理正则表达式,它可以帮助我们在字符串中查找、替换、分割、匹配复杂模式等操作。本文将全面介绍 Python 中正则表达式的使用,包括基础语法、常用操作符、实用技巧,并配有代码实例,帮助大家深入理解。

正则表达式基础

正则表达式是由一系列特殊字符构成的模式,用于匹配、查找和操作字符串中的内容。通过正则表达式,您可以描述一个复杂的字符串模式,然后使用 re 模块来搜索或替换文本。

1. 常见的正则表达式字符

1.1 字符匹配
  • .:匹配除换行符以外的任何单个字符。
  • []:匹配括号内的任何字符。例如,[a-z] 匹配任何小写字母。
  • ^:匹配字符串的开头。例如,^abc 匹配以 abc 开头的字符串。
  • $:匹配字符串的结尾。例如,abc$ 匹配以 abc 结尾的字符串。
  • 1.2 数量词
  • *:匹配前面的元素零次或多次。例如,a* 匹配空字符串、“a”、“aa”、“aaa” 等。
  • +:匹配前面的元素一次或多次。例如,a+ 匹配 “a”、“aa”、“aaa” 等,但不匹配空字符串。
  • ?:匹配前面的元素零次或一次。例如,a? 匹配空字符串或 “a”。
  • {n}:匹配前面元素恰好出现 n 次。例如,a{3} 匹配 “aaa”。
  • {n, m}:匹配前面元素出现 n 到 m 次。例如,a{1, 3} 匹配 “a”、“aa”、“aaa”。
  • 1.3 分组和选择
  • ():分组,用于提取匹配的子串。例如,(abc)+ 匹配一个或多个 “abc”。
  • |:表示“或”运算,匹配左边或右边的表达式。例如,abc|def 匹配 “abc” 或 “def”。
  • 1.4 特殊字符
  • \d:匹配一个数字,等同于 [0-9]
  • \D:匹配一个非数字字符。
  • \w:匹配一个字母数字字符,等同于 [a-zA-Z0-9_]
  • \W:匹配一个非字母数字字符。
  • \s:匹配任何空白字符,包括空格、制表符、换行符等。
  • \S:匹配任何非空白字符。
  • 1.5 转义字符

    有些字符在正则表达式中有特殊意义,如果你想匹配这些字符本身,需要使用反斜杠 \ 进行转义。例如,\. 用于匹配一个字面上的点,而不是任意字符。

    re模块基础操作

    Python 提供了 re 模块,允许我们利用正则表达式对字符串进行处理。常见的操作包括匹配、查找、替换、分割等。

    2.1 re.match():从字符串开头开始匹配

    re.match() 函数尝试从字符串的起始位置进行匹配。如果匹配成功,返回一个匹配对象;如果匹配失败,返回 None

    import re
    
    pattern = r"^abc"
    text = "abcdef"
    match = re.match(pattern, text)
    
    if match:
        print(f"匹配成功: {match.group()}")
    else:
        print("匹配失败")
    

    输出:

    匹配成功: abc
    

    2.2 re.search():在整个字符串中搜索匹配项

    re.search() 会在整个字符串中查找第一个匹配项。如果找到匹配项,返回一个匹配对象;否则,返回 None

    pattern = r"abc"
    text = "123abcdef"
    search = re.search(pattern, text)
    
    if search:
        print(f"找到匹配: {search.group()}")
    else:
        print("未找到匹配")
    

    输出:

    找到匹配: abc
    

    2.3 re.findall():查找所有匹配项

    re.findall() 返回字符串中所有与正则表达式匹配的子串,结果以列表形式返回。如果没有找到匹配项,返回空列表。

    pattern = r"\d+"  # 匹配所有数字
    text = "There are 123 apples and 456 bananas."
    result = re.findall(pattern, text)
    
    print(result)
    

    输出:

    ['123', '456']
    

    2.4 re.sub():替换匹配项

    re.sub() 用于将字符串中的匹配项替换为指定的内容。

    pattern = r"\d+"
    text = "My phone number is 1234567890."
    replaced_text = re.sub(pattern, "**********", text)
    
    print(replaced_text)
    

    输出:

    My phone number is **********.
    

    2.5 re.split():根据正则表达式分割字符串

    re.split() 根据正则表达式将字符串分割为多个子字符串。

    pattern = r"\s+"  # 匹配一个或多个空白字符
    text = "Hello   world   Python"
    result = re.split(pattern, text)
    
    print(result)
    

    输出:

    ['Hello', 'world', 'Python']
    

    高级技巧

    3.1 捕获组和非捕获组

    正则表达式中的括号不仅用于分组,还可以用于提取匹配的子串。捕获组(通过括号 () 定义)用于提取子字符串,而非捕获组(通过 (?:) 定义)只用于分组,但不提取。

    pattern = r"(\d+)-(\d+)"
    text = "My numbers are 123-456."
    match = re.search(pattern, text)
    
    if match:
        print(f"捕获组1: {match.group(1)}")
        print(f"捕获组2: {match.group(2)}")
    

    输出:

    捕获组1: 123
    捕获组2: 456
    

    3.2 正向先行断言(Lookahead)和正向后行断言(Lookbehind)

  • 正向先行断言(?=...))用于检查后面是否匹配某个模式。
  • 正向后行断言(?<=...))用于检查前面是否匹配某个模式。
  • # 正向先行断言:匹配后面跟着 "bar" 的 "foo"
    pattern = r"foo(?=bar)"
    text = "foobar"
    result = re.search(pattern, text)
    
    if result:
        print("匹配成功")
    

    输出:

    匹配成功
    

    3.3 使用 re.IGNORECASE 进行不区分大小写的匹配

    re.IGNORECASE(或 re.I)可以用于忽略大小写进行匹配。

    pattern = r"hello"
    text = "HELLO world"
    result = re.search(pattern, text, re.IGNORECASE)
    
    if result:
        print("匹配成功")
    

    输出:

    匹配成功
    

    正则表达式的性能优化

    正则表达式在处理大量数据时可能会遇到性能瓶颈,尤其是在复杂的模式匹配和字符串操作时。为了确保正则表达式在实际应用中的高效性,了解一些优化策略至关重要。以下是几种常见的性能优化方法。

    4.1 使用非贪婪匹配

    正则表达式默认采用贪婪模式(greedy),即尽可能多地匹配字符。如果你只需要匹配尽可能少的字符,可以使用非贪婪模式(lazy match),通过在量词后加 ? 来实现。

    贪婪模式
    import re
    
    text = "The quick brown fox jumps over the lazy dog."
    pattern = r"quick.*fox"
    result = re.search(pattern, text)
    print(result.group())
    

    输出:

    quick brown fox jumps
    
    非贪婪模式
    pattern = r"quick.*?fox"
    result = re.search(pattern, text)
    print(result.group())
    

    输出:

    quick brown fox
    

    非贪婪模式 .*? 会尽早匹配到 “fox” 而不是尽可能多地匹配字符,从而提升效率。

    4.2 使用原子组优化正则表达式

    在正则表达式中,某些子表达式的计算可以被称为“原子操作”,这意味着它们在执行时不会回溯。通过优化表达式使其尽量避免回溯,可以显著提升匹配速度。原子组(atomic group)通过 ?> 来标识。

    pattern = r"(?>abc|def)ghi"
    text = "defghi"
    result = re.search(pattern, text)
    
    if result:
        print("匹配成功")
    

    在这个例子中,(?>abc|def) 是一个原子组,它会一次性匹配 “abc” 或 “def” 之一,而不会回溯到其他部分。

    4.3 避免不必要的分组

    每次使用括号进行分组时,正则表达式都会产生一个捕获组。如果不需要捕获组,仅用于分组,可以使用非捕获组 (?:...) 来避免不必要的开销。

    # 使用捕获组
    pattern = r"(abc|def)ghi"
    # 使用非捕获组
    pattern = r"(?:abc|def)ghi"
    

    非捕获组 (?:...) 不会生成捕获对象,这样可以减少匹配过程中的额外开销。

    4.4 尽量避免复杂的正则表达式

    虽然正则表达式能够处理复杂的匹配任务,但在处理性能要求较高的任务时,应尽量避免使用过于复杂的正则表达式。复杂的正则表达式通常会导致较长的执行时间和过多的回溯。

    例如,使用多个嵌套分组或使用过多的回溯可能会极大影响性能。为了提高性能,尝试将复杂的正则表达式拆解成多个简单的表达式,或者采用其他高效的字符串处理方法。

    正则表达式中的断言(Assertions)

    正则表达式的断言(assertions)是一种用于匹配前后关系的高级技巧,允许我们不消耗字符本身,仅仅是检查字符是否满足某种条件。断言分为正向断言和反向断言。

    5.1 正向断言(Lookahead)

    正向断言用于检查某个模式是否出现在当前位置后面,但并不消耗匹配字符。使用 (?=...) 来表示正向断言。

    import re
    
    pattern = r"foo(?=bar)"
    text = "foobar"
    result = re.search(pattern, text)
    
    if result:
        print("匹配成功")
    

    输出:

    匹配成功
    

    这里,正向断言 (?=bar) 会检查 “foo” 后面是否跟着 “bar”,但并不会消耗字符。

    5.2 反向断言(Lookbehind)

    反向断言用于检查某个模式是否出现在当前位置前面,同样也不消耗字符。使用 (?<=...) 来表示反向断言。

    pattern = r"(?<=@)\w+"
    text = "email@example.com"
    result = re.search(pattern, text)
    
    if result:
        print(f"匹配成功: {result.group()}")
    

    输出:

    匹配成功: example
    

    这里,反向断言 (?<=@) 会检查当前字符前面是否有 “@”,并匹配紧随其后的单词字符。

    5.3 负向断言(Negative Lookahead 和 Negative Lookbehind)

    负向断言用于检查某个模式是否不存在于当前位置后面(负向正向断言)或前面(负向反向断言)。负向断言的语法分别是 (?!...)(?<!...)

    负向正向断言(Negative Lookahead)
    pattern = r"foo(?!bar)"
    text = "foobar"
    result = re.search(pattern, text)
    
    if result:
        print("匹配成功")
    else:
        print("未匹配成功")
    

    输出:

    未匹配成功
    

    这里,负向正向断言 (?!bar) 检查 “foo” 后面不跟着 “bar”。由于 “foobar” 后跟着 “bar”,所以匹配失败。

    负向反向断言(Negative Lookbehind)
    pattern = r"(?<!@)\w+"
    text = "email@example.com"
    result = re.search(pattern, text)
    
    if result:
        print(f"匹配成功: {result.group()}")
    

    输出:

    匹配成功: example
    

    在这里,负向反向断言 (?<!@) 确保当前字符前面不是 “@”,从而匹配字符串中的 “example”。

    正则表达式与多行匹配

    正则表达式默认是逐行匹配的,但可以通过设置 re.MULTILINEre.DOTALL 来改变默认的行为。

    6.1 使用 re.MULTILINE

    re.MULTILINE 使得 ^$ 可以匹配每一行的开头和结尾,而不仅仅是字符串的开头和结尾。

    pattern = r"^foo"
    text = """foo bar
    baz foo
    foo qux"""
    result = re.findall(pattern, text, re.MULTILINE)
    
    print(result)
    

    输出:

    ['foo', 'foo', 'foo']
    

    6.2 使用 re.DOTALL

    re.DOTALL 使得 . 匹配包括换行符在内的所有字符。默认情况下,. 不匹配换行符。

    pattern = r"foo.bar"
    text = """foo
    bar"""
    result = re.search(pattern, text, re.DOTALL)
    
    if result:
        print(f"匹配成功: {result.group()}")
    

    输出:

    匹配成功: foo\nbar
    

    在这个例子中,re.DOTALL 使得 . 匹配了换行符,从而匹配成功。

    总结

    在本文中,我们详细探讨了 Python 中正则表达式的基础和高级用法,从简单的匹配、查找、替换到断言、优化技巧及多行匹配等多个方面进行了深入讲解。正则表达式是一个强大的文本处理工具,掌握它的使用能够显著提高工作效率。希望这篇完整的指南能够帮助你更好地理解和应用正则表达式,提升你在 Python 中处理文本的能力。

    作者:一键难忘

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python中的正则表达式完全指南

    发表回复