MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python正则中的重复、特殊字符与分组

2021-07-312.8k 阅读

重复字符

在Python的正则表达式中,重复字符用于指定前面的字符或字符组出现的次数。这些重复字符为我们在匹配复杂字符串模式时提供了极大的灵活性。

基本重复字符

  1. *:表示前面的字符或字符组可以出现0次或多次。 例如,假设我们要匹配包含零个或多个字母a的字符串。
import re

pattern = 'a*'
strings = ['', 'a', 'aa', 'aaa']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

在上述代码中,re.search函数在每个字符串中搜索匹配a*模式的子串。空字符串匹配,因为a可以出现0次;包含一个或多个a的字符串也匹配,因为a可以出现多次。

  1. +:表示前面的字符或字符组必须出现1次或多次。 例如,匹配包含一个或多个字母a的字符串。
import re

pattern = 'a+'
strings = ['', 'a', 'aa', 'aaa']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

这里空字符串不匹配,因为a至少要出现1次,而包含a的字符串都匹配。

  1. ?:表示前面的字符或字符组可以出现0次或1次。 例如,匹配包含0个或1个字母a的字符串。
import re

pattern = 'a?'
strings = ['', 'a', 'aa', 'aaa']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

空字符串和只包含一个a的字符串匹配,而包含多个a的字符串不匹配,因为a最多只能出现1次。

限定重复次数

  1. {n}:表示前面的字符或字符组必须恰好出现n次。 例如,匹配恰好包含3个字母a的字符串。
import re

pattern = 'a{3}'
strings = ['', 'a', 'aa', 'aaa', 'aaaa']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

只有包含3个a的字符串匹配,其他字符串都不匹配。

  1. {n,}:表示前面的字符或字符组必须出现n次或更多次。 例如,匹配包含3个或更多字母a的字符串。
import re

pattern = 'a{3,}'
strings = ['', 'a', 'aa', 'aaa', 'aaaa']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

包含3个或更多a的字符串匹配,而包含少于3个a的字符串不匹配。

  1. {n,m}:表示前面的字符或字符组必须出现至少n次,但不超过m次。 例如,匹配包含2到4个字母a的字符串。
import re

pattern = 'a{2,4}'
strings = ['', 'a', 'aa', 'aaa', 'aaaa', 'aaaaa']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

包含2到4个a的字符串匹配,而包含少于2个或多于4个a的字符串不匹配。

贪婪与非贪婪匹配

在正则表达式中,重复字符默认是贪婪的,即尽可能多地匹配字符。 例如,考虑字符串'aaaaa'和模式a{2,4}。贪婪匹配会匹配4个a,因为它会尝试匹配尽可能多的字符,只要不超过上限。

import re

pattern = 'a{2,4}'
string = 'aaaaa'
match = re.search(pattern, string)
if match:
    print(match.group())

输出为aaaa

然而,有时我们希望匹配尽可能少的字符,这就是非贪婪匹配。在重复字符后加上?就可以实现非贪婪匹配。 对于同样的字符串'aaaaa'和模式a{2,4}?,非贪婪匹配会匹配2个a

import re

pattern = 'a{2,4}?'
string = 'aaaaa'
match = re.search(pattern, string)
if match:
    print(match.group())

输出为aa

特殊字符

正则表达式中有许多特殊字符,它们具有特定的含义,用于构建复杂的匹配模式。

字符类

  1. []:字符类用于匹配方括号内的任意一个字符。 例如,[abc]匹配abc中的任意一个字符。
import re

pattern = '[abc]'
strings = ['a', 'b', 'c', 'd']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

包含abc的字符串匹配,而包含d的字符串不匹配。

可以在字符类中使用范围,如[a - z]匹配任意小写字母,[0 - 9]匹配任意数字。

import re

pattern = '[a - z]'
strings = ['a', 'Z', '5']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

这里只有小写字母a匹配,大写字母Z和数字5不匹配。

  1. [^]:取反字符类,匹配不在方括号内的任意一个字符。 例如,[^abc]匹配除了abc之外的任意字符。
import re

pattern = '[^abc]'
strings = ['a', 'b', 'c', 'd']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

这里只有字符d匹配,abc不匹配。

预定义字符类

  1. .:匹配除换行符\n之外的任意一个字符。 例如,a.c匹配a后跟任意一个字符(除\n)再跟c的字符串。
import re

pattern = 'a.c'
strings = ['abc', 'a c', 'a1c', 'a\nc']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

abca ca1c匹配,而a\nc不匹配。

  1. \d:匹配任意一个数字字符,等价于[0 - 9]
import re

pattern = '\d'
strings = ['1', 'a', '5']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

数字15匹配,字母a不匹配。

  1. \D:匹配任意一个非数字字符,等价于[^0 - 9]
import re

pattern = '\D'
strings = ['1', 'a', '5']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

字母a匹配,数字15不匹配。

  1. \s:匹配任意一个空白字符,包括空格、制表符、换行符等,等价于[ \t\n\r\f\v]
import re

pattern = '\s'
strings = [' ', '\t', 'a']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

空格 和制表符\t匹配,字母a不匹配。

  1. \S:匹配任意一个非空白字符,等价于[^ \t\n\r\f\v]
import re

pattern = '\S'
strings = [' ', '\t', 'a']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

字母a匹配,空格 和制表符\t不匹配。

  1. \w:匹配任意一个单词字符,包括字母、数字和下划线,等价于[a - zA - Z0 - 9_]
import re

pattern = '\w'
strings = ['a', '1', '_', ' ']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

字母a、数字1和下划线_匹配,空格 不匹配。

  1. \W:匹配任意一个非单词字符,等价于[^a - zA - Z0 - 9_]
import re

pattern = '\W'
strings = ['a', '1', '_', ' ']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

空格 匹配,字母a、数字1和下划线_不匹配。

边界匹配

  1. ^:匹配字符串的开头。 例如,^a匹配以字母a开头的字符串。
import re

pattern = '^a'
strings = ['aabc', 'babc']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

aabc匹配,因为它以a开头,而babc不匹配。

  1. $:匹配字符串的结尾。 例如,c$匹配以字母c结尾的字符串。
import re

pattern = 'c$'
strings = ['abc', 'abbc']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

abc匹配,因为它以c结尾,而abbc不匹配。

  1. \b:匹配单词边界,即单词和非单词字符之间的位置,或者字符串开头或结尾的位置。 例如,\bcat\b匹配独立的单词cat,而不是category中的cat
import re

pattern = '\bcat\b'
strings = ['cat', 'category', 'the cat']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

catthe cat中的cat匹配,而category中的cat不匹配。

  1. \B:匹配非单词边界。 例如,\Bcat\B匹配不在单词边界的cat,如category中的cat
import re

pattern = '\Bcat\B'
strings = ['cat', 'category', 'the cat']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

category中的cat匹配,而catthe cat中的cat不匹配。

分组

在正则表达式中,分组是一种强大的功能,它允许我们将多个字符或子模式组合在一起,作为一个整体进行操作。

基本分组

使用圆括号()来定义分组。例如,(ab)+表示ab这个组合可以出现1次或多次。

import re

pattern = '(ab)+'
strings = ['', 'ab', 'abab', 'aab']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

包含一个或多个ab组合的字符串ababab匹配,而空字符串和aab不匹配。

捕获组

捕获组是最常见的分组类型,它会记住匹配的内容。在Python中,可以使用group()方法来获取捕获组匹配的内容。 例如,假设我们有一个字符串格式为name:age,我们想分别获取名字和年龄。

import re

pattern = '(\w+):(\d+)'
string = 'John:25'
match = re.search(pattern, string)
if match:
    name = match.group(1)
    age = match.group(2)
    print(f"名字: {name}, 年龄: {age}")

这里(\w+)是第一个捕获组,匹配名字部分;(\d+)是第二个捕获组,匹配年龄部分。group(1)获取第一个捕获组匹配的内容,group(2)获取第二个捕获组匹配的内容。

非捕获组

有时候我们只是想将一些子模式组合在一起,但不想捕获它们匹配的内容,这时可以使用非捕获组。非捕获组的语法是(?:pattern)。 例如,(?:ab)+表示ab组合出现1次或多次,但不会捕获ab的匹配内容。

import re

pattern = '(?:ab)+'
string = 'abab'
match = re.search(pattern, string)
if match:
    print("匹配成功,但没有捕获组内容")

这里虽然匹配成功,但无法通过group()方法获取ab的匹配内容,因为它是非捕获组。

反向引用

反向引用允许我们在正则表达式中引用之前定义的捕获组。语法是\number,其中number是捕获组的编号。 例如,假设我们要匹配重复的单词,如word word

import re

pattern = '(\w+)\s+\1'
strings = ['hello hello', 'hello world']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

(\w+)是第一个捕获组,匹配一个单词。\s+匹配一个或多个空白字符。\1是反向引用,引用第一个捕获组匹配的内容,所以整个模式匹配重复的单词。因此,hello hello匹配,而hello world不匹配。

分组与重复字符的结合

分组可以与重复字符一起使用,以实现更复杂的模式匹配。例如,(abc){2}表示abc这个组合必须恰好出现2次。

import re

pattern = '(abc){2}'
strings = ['abcabc', 'abc', 'abcab']

for string in strings:
    if re.search(pattern, string):
        print(f"'{string}' 匹配成功")
    else:
        print(f"'{string}' 匹配失败")

只有abcabc匹配,因为它包含两个abc组合,而abcabcab不匹配。

通过合理运用重复字符、特殊字符和分组,我们可以在Python正则表达式中构建出极其复杂且强大的匹配模式,满足各种文本处理的需求。无论是数据提取、文本验证还是字符串替换,这些正则表达式的特性都为我们提供了高效的解决方案。在实际应用中,需要根据具体的需求仔细设计正则表达式,同时注意性能和准确性的平衡。例如,在处理大量文本时,过于复杂的正则表达式可能会导致性能下降,因此需要进行优化。此外,对于一些模糊的匹配需求,可能需要结合具体的业务逻辑来确定最合适的正则表达式。同时,在使用捕获组和反向引用时,要确保其逻辑正确,避免出现错误的引用或不必要的捕获,以提高代码的可读性和可维护性。总之,深入理解和熟练运用Python正则表达式中的这些特性,将大大提升我们在文本处理领域的编程能力。