轻松掌握 Python 正则表达式使用方法 – wiki基地


轻松掌握 Python 正则表达式使用方法

正则表达式(Regular Expression,简称 Regex 或 RE)是一种强大的文本处理工具,它使用一套特殊的字符组合(模式)来匹配、查找、替换或验证文本字符串。想象一下,如果你需要在海量文本中找出所有符合特定格式的电话号码、邮箱地址,或者批量替换掉文档中的特定词汇,手动操作无疑效率低下且容易出错。这时,正则表达式就成了你的得力助手。

Python通过其内置的 re 模块提供了完整的正则表达式支持。虽然初学时正则表达式的语法可能看起来有些神秘甚至令人生畏,但一旦掌握了其基本原理和常用规则,你会发现它能极大地提升你的文本处理能力。

本文将带你循序渐进地学习如何在 Python 中使用正则表达式,从最基础的概念到常用的函数和技巧,让你轻松掌握这项强大的技能。

第一部分:理解正则表达式的基础概念

在深入 Python 的 re 模块之前,我们首先需要理解正则表达式本身的“语言”。正则表达式由普通字符和元字符组成。普通字符(如字母、数字)只匹配它们自身,而元字符则具有特殊的含义。

1. 普通字符 (Literal Characters)

这是最简单的部分。大多数字符,如字母(a-z, A-Z)、数字(0-9)以及没有特殊含义的符号,都只匹配它们自己。

  • cat:只会精确匹配字符串中的 “cat”。
  • 123:只会精确匹配字符串中的 “123”。
  • hello world:只会精确匹配字符串中的 “hello world”。

2. 元字符 (Metacharacters)

元字符是正则表达式的魔法所在,它们不匹配自身,而是表示某种模式或位置。以下是一些最常用的元字符:

  • . (点号): 匹配除换行符之外的任意单个字符
    • a.b 可以匹配 “aab”, “acb”, “a0b”, “a#b” 等等,但不匹配 “ab” 或 “accdb”。
  • ^ (脱字符): 匹配字符串的开头
    • ^hello 只匹配以 “hello” 开头的字符串,如 “hello world”,但不匹配 “say hello”。
  • $ (美元符号): 匹配字符串的结尾
    • world$ 只匹配以 “world” 结尾的字符串,如 “hello world”,但不匹配 “world cup”。
  • * (星号): 匹配前一个字符出现零次或多次
    • a* 可以匹配 “”, “a”, “aa”, “aaa” 等等。
    • ab*c 可以匹配 “ac”, “abc”, “abbc”, “abbbc” 等等。
  • + (加号): 匹配前一个字符出现一次或多次
    • a+ 可以匹配 “a”, “aa”, “aaa” 等等,但不能匹配 “”。
    • ab+c 可以匹配 “abc”, “abbc”, “abbbc” 等等,但不能匹配 “ac”。
  • ? (问号): 匹配前一个字符出现零次或一次
    • a? 可以匹配 “” 或 “a”。
    • ab?c 可以匹配 “ac” 或 “abc”。
  • {m,n} (花括号): 匹配前一个字符出现至少 m 次,至多 n 次
    • a{3}:精确匹配 “aaa”。
    • a{2,4}:匹配 “aa”, “aaa”, “aaaa”。
    • a{2,}:匹配 “aa”, “aaa”, “aaaa”, … (至少两次)。
    • a{,5}:匹配 “”, “a”, “aa”, “aaa”, “aaaa”, “aaaaa” (至多五次)。
  • | (竖线): 匹配竖线左边或右边的表达式 (逻辑 OR)。
    • cat|dog 可以匹配 “cat” 或 “dog”。
  • () (圆括号): 分组。可以将多个字符视为一个整体进行操作,也用于捕获匹配的文本
    • (ab)+ 可以匹配 “ab”, “abab”, “ababab” 等等。
    • (cat|dog)s 可以匹配 “cats” 或 “dogs”。
  • [] (方括号): 字符集。匹配方括号中列出的任意一个字符
    • [abc]:匹配 “a”, “b”, 或 “c”。
    • [0-9]:匹配任意一个数字 (等同于 \d)。
    • [a-z]:匹配任意一个小写字母。
    • [A-Z]:匹配任意一个大写字母。
    • [a-zA-Z0-9]:匹配任意一个字母或数字。
    • [^abc]:在方括号内,^ 表示,匹配除了 “a”, “b”, “c” 之外的任意一个字符。
  • \ (反斜杠): 转义字符。用于取消元字符的特殊含义,使其匹配自身。
    • . 匹配任意字符,但 \. 只匹配字面量点号 .
    • * 是量词,但 \* 只匹配字面量星号 *
    • \\ 匹配字面量反斜杠 \

3. 常用的特殊序列 (Special Sequences)

为了方便,正则表达式提供了一些反斜杠加字符的组合,用于表示常见的字符集或位置。

  • \d: 匹配任意一个数字 (等同于 [0-9])。
  • \D: 匹配任意一个非数字字符 (等同于 [^0-9])。
  • \s: 匹配任意一个空白字符 (包括空格、制表符 \t、换行符 \n、回车符 \r 等)。
  • \S: 匹配任意一个非空白字符 (等同于 [^\s])。
  • \w: 匹配任意一个词字符 (包括字母、数字和下划线 _,等同于 [a-zA-Z0-9_])。
  • \W: 匹配任意一个非词字符 (等同于 [^\w])。
  • \b: 匹配词边界。词边界是指一个词字符和非词字符之间的位置,或者字符串的开头/结尾与词字符之间的位置。例如,\bword\b 可以匹配 “word” 在 “hello word!” 或 “the word is” 中的出现,但不匹配 “wordy” 或 “inward” 中的 “word”。
  • \B: 匹配非词边界

4. 贪婪与非贪婪匹配 (Greedy vs. Non-Greedy)

默认情况下,量词 (*, +, ?, {m,n}) 是贪婪的,它们会尽可能多地匹配字符。

  • 例如,对字符串 "<a>b<c>" 使用模式 <.*> 会匹配整个 "<a>b<c>",因为 .* 会一直匹配到最后一个 >

在量词后面加上一个 ? 可以使其变为非贪婪的(或称为懒惰的),它会尽可能少地匹配字符。

  • 例如,对字符串 "<a>b<c>" 使用模式 <.*?> 会分别匹配 "<a>""ệc>"*? 匹配零次或多次,但尽可能少。

非贪婪量词包括:*?, +?, ??, {m,n}?

第二部分:Python 的 re 模块

Python 通过 re 模块提供了与正则表达式交互的函数。使用这个模块通常包括导入它,然后调用其函数,传入正则表达式模式和要搜索的字符串。

python
import re

1. 生字符串 (Raw Strings): r”…”

在 Python 中处理正则表达式时,强烈建议使用生字符串(Raw Strings)。在普通字符串中,反斜杠 \ 是转义字符,比如 \n 表示换行。但在正则表达式中,反斜杠 \ 也有特殊含义(如 \d),如果你想匹配一个字面量反斜杠,你需要写 \\。这会导致反斜杠过多,使模式难以阅读。

生字符串以 r 开头(如 r"pattern"),它会忽略字符串中反斜杠的特殊含义,将反斜杠视为普通字符(除非它后面跟着引号本身)。

  • 普通字符串:要匹配 \d 这个序列,需要写 "\\d"
  • 生字符串:要匹配 \d 这个序列,只需要写 r"\d"

使用生字符串可以避免混淆,让你的正则表达式模式更清晰。

2. re 模块的主要函数

  • re.search(pattern, string, flags=0):

    • string搜索pattern 匹配的第一个位置
    • 如果找到匹配,返回一个 Match 对象;否则,返回 None
    • 这是最常用的查找函数,因为它会在整个字符串中查找匹配。

    “`python
    import re

    text = “hello world, python regex is powerful!”
    pattern = r”python”

    match = re.search(pattern, text)

    if match:
    print(“找到匹配项:”, match.group()) # 输出找到的匹配字符串
    print(“匹配起始位置:”, match.start())
    print(“匹配结束位置:”, match.end())
    print(“匹配范围:”, match.span())
    else:
    print(“未找到匹配项”)

    查找数字

    text2 = “There are 123 apples and 45 oranges.”
    pattern2 = r”\d+” # 匹配一个或多个数字
    match2 = re.search(pattern2, text2)
    if match2:
    print(“找到第一个数字:”, match2.group()) # 输出 “123”
    “`

  • re.match(pattern, string, flags=0):

    • 尝试从 string开头匹配 pattern
    • 如果字符串的开头与模式匹配,返回一个 Match 对象;否则,返回 None
    • 注意:与 search 的区别在于 match 只检查字符串的起始位置。

    “`python
    import re

    text = “hello world”
    pattern1 = r”hello”
    pattern2 = r”world”

    match1 = re.match(pattern1, text)
    match2 = re.match(pattern2, text)

    if match1:
    print(“match1 匹配成功:”, match1.group()) # 输出 “match1 匹配成功: hello”
    else:
    print(“match1 匹配失败”)

    if match2:
    print(“match2 匹配成功:”, match2.group())
    else:
    print(“match2 匹配失败”) # 输出 “match2 匹配失败” (因为 world 不在开头)

    对比 search

    search2 = re.search(pattern2, text)
    if search2:
    print(“search2 匹配成功:”, search2.group()) # 输出 “search2 匹配成功: world”
    “`

  • re.findall(pattern, string, flags=0):

    • string 中找到与 pattern 匹配的所有非重叠项
    • 返回一个列表
    • 如果模式中包含捕获组 (),则返回一个包含所有捕获组内容的列表(如果只有一个组,是字符串列表;如果有多个组,是元组列表)。
    • 如果模式中不包含捕获组,则返回一个包含所有完整匹配项的字符串列表。

    “`python
    import re

    text = “Emails: [email protected], [email protected], [email protected]

    匹配简单的邮箱格式

    pattern = r”\w+@\w+.\w+”
    emails = re.findall(pattern, text)
    print(“找到的邮箱:”, emails) # 输出 [‘[email protected]’, ‘[email protected]’, ‘[email protected]’]

    模式包含捕获组

    pattern_groups = r”(\w+)@(\w+).(\w+)”
    email_parts = re.findall(pattern_groups, text)
    print(“找到的邮箱 parts:”, email_parts)

    输出 [(‘test1’, ‘example’, ‘com’), (‘user’, ‘mail’, ‘org’), (‘info’, ‘website’, ‘net’)]

    text2 = “Numbers: 10, 25, -5, 99″
    pattern2 = r”\d+” # 匹配一个或多个数字 (无分组)
    numbers = re.findall(pattern2, text2)
    print(“找到的数字串:”, numbers) # 输出 [’10’, ’25’, ‘5’, ’99’] – 注意这里把 -5 分成了 ‘5’

    如果要匹配带符号的数字,模式需要调整,例如 r”-?\d+”

    pattern2_signed = r”-?\d+”
    numbers_signed = re.findall(pattern2_signed, text2)
    print(“找到的带符号数字串:”, numbers_signed) # 输出 [’10’, ’25’, ‘-5′, ’99’]

    “`

  • re.finditer(pattern, string, flags=0):

    • string 中找到与 pattern 匹配的所有非重叠项
    • 返回一个迭代器,迭代器中的每个元素都是一个 Match 对象。
    • 当处理大量匹配项,或者需要访问每个匹配项的详细信息(如位置或分组)时,finditerfindall 更高效,因为它不会一次性将所有结果加载到内存中。

    “`python
    import re

    text = “Emails: [email protected], [email protected], [email protected]
    pattern = r”(\w+)@(\w+).(\w+)”

    for match in re.finditer(pattern, text):
    print(“找到一个邮箱:”)
    print(” 完整匹配:”, match.group())
    print(” 用户名:”, match.group(1)) # 第一个捕获组
    print(” 域名:”, match.group(2)) # 第二个捕获组
    print(” 后缀:”, match.group(3)) # 第三个捕获组
    print(” 位置:”, match.span())
    “`

  • re.sub(pattern, repl, string, count=0, flags=0):

    • string 中查找所有与 pattern 匹配的子串,并将它们替换为 repl
    • repl 可以是一个字符串,也可以是一个函数。
    • count 指定最多替换多少次,0 表示替换所有匹配项(默认)。
    • 返回替换后的字符串
    • repl 字符串中,\g<组号>\g<组名> 可以引用捕获组的内容(如 \g<1> 引用第一个捕获组)。

    “`python
    import re

    text = “The quick brown fox jumps over the lazy fox.”
    pattern = r”fox”
    replacement = “dog”

    new_text = re.sub(pattern, replacement, text)
    print(“替换后的文本:”, new_text) # 输出 “The quick brown dog jumps over the lazy dog.”

    只替换第一个

    new_text_one = re.sub(pattern, replacement, text, count=1)
    print(“替换第一个:”, new_text_one) # 输出 “The quick brown dog jumps over the lazy fox.”

    使用捕获组进行替换 (例如,交换姓和名)

    name = “Doe, John”
    pattern_name = r”(\w+), (\w+)” # 捕获姓和名
    replacement_name = r”\g<2> \g<1>” # 使用 \g<组号> 引用捕获组,第二个组(名)在前,第一个组(姓)在后

    formatted_name = re.sub(pattern_name, replacement_name, name)
    print(“格式化姓名:”, formatted_name) # 输出 “John Doe”

    repl 可以是一个函数

    def double_number(match):
    number = int(match.group(0)) # 获取匹配到的数字字符串并转为整数
    return str(number * 2) # 返回翻倍后的字符串

    text_numbers = “Numbers: 10 and 25.”
    pattern_numbers = r”\d+”

    doubled_text = re.sub(pattern_numbers, double_number, text_numbers)
    print(“数字翻倍后的文本:”, doubled_text) # 输出 “Numbers: 20 and 50.”
    “`

  • re.split(pattern, string, maxsplit=0, flags=0):

    • 根据与 pattern 匹配的子串来分割 string
    • 返回一个字符串列表
    • maxsplit 指定最多进行多少次分割,0 表示不限制。
    • 注意: 如果模式包含捕获组 (),则捕获组匹配的内容也会包含在结果列表中。

    “`python
    import re

    text = “apple,banana;orange grape”

    使用逗号、分号或空格作为分隔符

    pattern = r”[,; ]”
    fruits = re.split(pattern, text)
    print(“分割后的水果列表:”, fruits) # 输出 [‘apple’, ‘banana’, ‘orange’, ‘grape’]

    maxsplit 限制分割次数

    text2 = “a:b:c:d”
    pattern2 = r”:”
    parts = re.split(pattern2, text2, maxsplit=2)
    print(“限制分割次数:”, parts) # 输出 [‘a’, ‘b’, ‘c:d’]

    模式包含捕获组

    text3 = “hello(world)python”
    pattern3 = r”([()])” # 匹配并捕获括号
    parts_with_groups = re.split(pattern3, text3)
    print(“包含捕获组的分割:”, parts_with_groups) # 输出 [‘hello’, ‘(‘, ‘world’, ‘)’, ‘python’]
    “`

  • re.compile(pattern, flags=0):

    • 将正则表达式模式编译成一个正则表达式对象。
    • 编译后的对象具有与 re 模块函数同名的方法(如 search(), findall(), sub() 等)。
    • 优点:
      • 效率更高: 如果在同一个程序中多次使用同一个正则表达式模式,编译可以避免重复解析模式,提高效率。
      • 代码更清晰: 可以将模式存储在一个变量中,使代码更易读。

    “`python
    import re

    编译模式

    email_pattern_compiled = re.compile(r”(\w+)@(\w+).(\w+)”)

    text1 = “User email is [email protected]
    text2 = “Admin email is [email protected]

    使用编译后的对象的方法

    match1 = email_pattern_compiled.search(text1)
    if match1:
    print(“From text1:”, match1.group())

    match2 = email_pattern_compiled.search(text2)
    if match2:
    print(“From text2:”, match2.group())

    对比 findall 方法

    text_all = “Emails: [email protected], [email protected], [email protected]
    all_emails = email_pattern_compiled.findall(text_all)
    print(“所有邮箱:”, all_emails)
    “`

3. Match 对象的方法

re.search()re.match() 找到匹配时,它们返回一个 Match 对象。这个对象包含了匹配的详细信息。

  • match.group(0)match.group(): 返回整个匹配到的字符串。
  • match.group(n): 返回第 n 个捕获组(由圆括号定义的组)匹配到的字符串。组号从 1 开始计数。
  • match.groups(): 返回一个元组,包含所有捕获组匹配到的字符串。
  • match.start(n=0): 返回第 n 个组匹配到的子串在原字符串中的起始索引。n=0 表示整个匹配项。
  • match.end(n=0): 返回第 n 个组匹配到的子串在原字符串中的结束索引(不包含)。n=0 表示整个匹配项。
  • match.span(n=0): 返回一个元组 (start, end),表示第 n 个组匹配到的子串在原字符串中的起始和结束索引。n=0 表示整个匹配项。

“`python
import re

text = “My number is 123-456-7890.”

模式捕获区号、前缀和行号

pattern = r”(\d{3})-(\d{3})-(\d{4})”

match = re.search(pattern, text)

if match:
print(“完整匹配:”, match.group(0)) # 或 match.group() -> 123-456-7890
print(“区号:”, match.group(1)) # -> 123
print(“前缀:”, match.group(2)) # -> 456
print(“行号:”, match.group(3)) # -> 7890
print(“所有分组:”, match.groups()) # -> (‘123’, ‘456’, ‘7890’)

print("完整匹配位置:", match.span(0))   # 或 match.span() -> (13, 25)
print("区号位置:", match.span(1))      # -> (13, 16)
print("完整匹配起始索引:", match.start(0)) # 或 match.start() -> 13
print("完整匹配结束索引:", match.end(0))   # 或 match.end() -> 25

“`

4. 正则表达式标志 (Flags)

re 模块的函数和编译方法都接受一个可选的 flags 参数,用于修改匹配的行为。常用的标志包括:

  • re.IGNORECASEre.I: 进行忽略大小写的匹配。
    • re.search(r"python", "Python", re.I) 会匹配成功。
  • re.MULTILINEre.M: 使 ^$ 匹配每一行的开头和结尾,而不仅仅是整个字符串的开头和结尾。在多行文本中查找模式时非常有用。
    • 没有 re.M 时,^abc$ 只匹配 “abc” (整个字符串只有abc)。
    • 使用 re.M 时,对于字符串 "abc\ndef\nghi", ^def$ 会匹配成功。
  • re.DOTALLre.S: 使点号 . 匹配包括换行符 \n 在内的所有字符。默认情况下,. 不匹配换行符。
    • re.search(r"a.b", "a\nb") 不会匹配成功(默认)。
    • re.search(r"a.b", "a\nb", re.S) 会匹配成功。
  • re.VERBOSEre.X: 允许你在正则表达式中添加空格和注释,提高模式的可读性。在写复杂模式时非常有用。

    “`python
    import re

    不使用 VERBOSE

    pattern1 = r”^(\d{3})-(\d{3})-(\d{4})$”

    使用 VERBOSE,添加空格和注释

    pattern2 = r”””
    ^ # 匹配字符串开头
    (\d{3}) # 捕获区号 (三个数字)
    – # 匹配连字符
    (\d{3}) # 捕获前缀 (三个数字)
    – # 匹配连字符
    (\d{4}) # 捕获行号 (四个数字)
    $ # 匹配字符串结尾
    “””

    phone_number = “123-456-7890”

    match1 = re.match(pattern1, phone_number)
    match2 = re.match(pattern2, phone_number, re.VERBOSE)

    print(“使用普通模式:”, match1.group() if match1 else “不匹配”)
    print(“使用 VERBOSE 模式:”, match2.group() if match2 else “不匹配”)

    忽略大小写

    match_case = re.search(r”apple”, “Apple”, re.IGNORECASE)
    print(“忽略大小写匹配:”, match_case.group() if match_case else “不匹配”)

    多行匹配

    text_multiline = “Line 1\nLine 2\nLine 3”

    匹配以 Line 2 结尾的行

    match_multiline = re.search(r”Line 2$”, text_multiline, re.MULTILINE)
    print(“多行匹配:”, match_multiline.group() if match_multiline else “不匹配”)

    点号匹配换行符

    text_dotall = “a\nb”
    match_dotall_no_s = re.search(r”a.b”, text_dotall)
    match_dotall_s = re.search(r”a.b”, text_dotall, re.DOTALL)
    print(“点号不匹配换行:”, match_dotall_no_s) # None
    print(“点号匹配换行:”, match_dotall_s.group() if match_dotall_s else “不匹配”) # a\nb
    “`

可以将多个标志用按位 OR | 组合起来使用,例如 re.I | re.M

第三部分:进阶技巧与最佳实践

掌握了基础概念和函数后,一些进阶技巧和最佳实践能让你更高效地使用正则表达式。

1. 非捕获组 (Non-capturing Groups): (?:...)

圆括号 () 默认既用于分组,也用于捕获。如果你只需要分组而不需要捕获(即不想在 match.groups()re.findall() 的结果中包含这部分匹配),可以使用非捕获组 (?:...)。这有时能稍微提高性能,并且在使用 findall 时可以改变返回列表的结构。

“`python
import re

text = “cat dog mouse”

捕获组 (cat|dog) 会被 findall 捕获

pattern1 = r”(cat|dog)\s+\w+”
result1 = re.findall(pattern1, text)
print(“捕获组 findall:”, result1) # 输出 [‘cat’, ‘dog’] – 只返回括号内的内容

非捕获组 (?:cat|dog) 只用于分组,不捕获

pattern2 = r”(?:cat|dog)\s+\w+”
result2 = re.findall(pattern2, text)
print(“非捕获组 findall:”, result2) # 输出 [‘cat dog’, ‘dog mouse’] – 返回整个匹配项
“`

2. 命名捕获组 (Named Capture Groups): (?P<name>...)

使用数字索引获取捕获组内容(如 match.group(1)) 在模式复杂时容易出错。命名捕获组允许你为捕获组指定一个名字,然后通过名字来获取内容,这大大提高了可读性。

“`python
import re

text = “Date: 2023-10-26”

命名捕获年、月、日

pattern = r”(?P\d{4})-(?P\d{2})-(?P\d{2})”

match = re.search(pattern, text)

if match:
print(“年:”, match.group(“year”)) # 通过名字访问
print(“月:”, match.group(“month”)) # 通过名字访问
print(“日:”, match.group(“day”)) # 通过名字访问
print(“所有命名分组:”, match.groupdict()) # 返回一个字典 {name: value}
“`

re.sub() 的替换字符串中,可以使用 \g<name> 来引用命名捕获组。

“`python
import re

name = “Doe, John”
pattern_name = r”(?P\w+), (?P\w+)”
replacement_name = r”\g \g

formatted_name = re.sub(pattern_name, replacement_name, name)
print(“使用命名分组替换:”, formatted_name) # 输出 “John Doe”
“`

3. 测试你的正则表达式

写复杂的正则表达式时,很容易出错。使用在线的正则表达式测试工具(如 regex101.com, regexr.com)可以帮助你实时构建和测试模式,查看每个部分的匹配效果,以及解释模式的含义。这比在 Python 中反复运行代码效率高得多。

4. 逐步构建复杂模式

不要试图一次性写出一个很长的复杂模式。从简单的部分开始,逐步添加元字符、量词和分组,并在测试工具或代码中验证每一步的效果。

5. 性能考虑

  • 如前所述,对于需要多次使用的模式,使用 re.compile() 可以提高效率。
  • 避免过度使用复杂的先行断言/后行断言(lookarounds)如果可以用更简单的方式实现。
  • 小心使用 .*.+ 这样的贪婪模式,它们可能导致“回溯失控”(catastrophic backtracking),使得匹配过程变得非常慢,尤其是在处理长字符串时。在可能的情况下,使用非贪婪模式 .*?.+?,或者更精确地指定允许匹配的字符集(如 [^>]* 匹配除了 > 之外的任意字符零次或多次)。

6. 清晰度和可维护性

  • 为你的模式变量选择有意义的名字。
  • 对于特别复杂的模式,考虑使用 re.VERBOSE 标志来添加注释和格式。
  • 在代码中添加注释,解释模式的用途和关键部分。

第四部分:实践案例(综合应用)

让我们通过一个稍微复杂一些的例子来综合应用学到的知识。假设我们要从一段文本中提取所有符合 YYYY-MM-DDMM/DD/YYYY 格式的日期。

“`python
import re

text = “””
Meeting scheduled for 2023-10-26.
The project started on 04/15/2022.
Deadline is 2024-01-31.
Another date: 12/01/2023.
Invalid format: 2023/10/26 or 10-26-2023.
“””

构建模式:

格式1: YYYY-MM-DD

\d{4} 匹配年份 (4个数字)

– 匹配连字符

\d{2} 匹配月份 (2个数字)

– 匹配连字符

\d{2} 匹配日期 (2个数字)

格式2: MM/DD/YYYY

\d{2} 匹配月份 (2个数字)

/ 匹配斜杠

\d{2} 匹配日期 (2个数字)

/ 匹配斜杠

\d{4} 匹配年份 (4个数字)

使用 | 连接两种格式,使用非捕获组 (?:…) 将它们组合

date_pattern = re.compile(r”(?:\d{4}-\d{2}-\d{2})|(?:\d{2}/\d{2}/\d{4})”)

查找所有匹配项

found_dates = date_pattern.findall(text)

print(“找到的日期:”, found_dates)

输出: 找到的日期: [‘2023-10-26′, ’04/15/2022’, ‘2024-01-31′, ’12/01/2023’]

如果我们需要提取年、月、日,可以使用捕获组并使用 finditer 或 search

date_pattern_groups = re.compile(r”(\d{4})-(\d{2})-(\d{2})|(\d{2})/(\d{2})/(\d{4})”)

print(“\n按部分提取日期:”)
for match in date_pattern_groups.finditer(text):
# match.groups() 会返回一个包含所有捕获组的元组,对于这个模式,
# 如果匹配的是 YYYY-MM-DD,则前三个组有值,后三个组为 None
# 如果匹配的是 MM/DD/YYYY,则前三个组为 None,后三个组有值
year, month, day, month2, day2, year2 = match.groups()

if year: # 匹配 YYYY-MM-DD 格式
    print(f"  格式 YYYY-MM-DD: 年={year}, 月={month}, 日={day}")
elif year2: # 匹配 MM/DD/YYYY 格式
     print(f"  格式 MM/DD/YYYY: 年={year2}, 月={month2}, 日={day2}")

使用 sub 将日期格式统一替换为 YYYY/MM/DD

我们需要处理两种格式的匹配

def format_date(match):
year1, month1, day1, month2, day2, year2 = match.groups()
if year1: # 匹配 YYYY-MM-DD
return f”{year1}/{month1}/{day1}”
elif year2: # 匹配 MM/DD/YYYY
return f”{year2}/{month2}/{day2}”
return match.group(0) # 如果出于某种原因没有匹配到组 (尽管在这里应该不会发生)

注意这里 sub 的 pattern 和 finditer 使用的 pattern 是一样的 (有捕获组)

formatted_text = re.sub(date_pattern_groups, format_date, text)

print(“\n格式化后的文本:”)
print(formatted_text)

输出:

Meeting scheduled for 2023/10/26.

The project started on 2022/04/15.

Deadline is 2024/01/31.

Another date: 2023/12/01.

Invalid format: 2023/10/26 or 10-26-2023.

注意 invalid format 的第一个日期被替换了,因为它匹配了 YYYY-MM-DD 的一部分 \d{4}/\d{2}/\d{2} -> 2023/10/26

为了更严格的匹配,需要在模式中添加词边界 \b

date_pattern_strict = re.compile(r”\b(?P\d{4})-(?P\d{2})-(?P\d{2})\b|\b(?P\d{2})/(?P\d{2})/(?P\d{4})\b”)

def format_date_strict(match):
if match.group(‘y1’): # 匹配 YYYY-MM-DD
return f”{match.group(‘y1’)}/{match.group(‘m1’)}/{match.group(‘d1’)}”
elif match.group(‘y2’): # 匹配 MM/DD/YYYY
return f”{match.group(‘y2’)}/{match.group(‘m2’)}/{match.group(‘d2’)}”
return match.group(0)

formatted_text_strict = re.sub(date_pattern_strict, format_date_strict, text)
print(“\n使用严格模式和命名分组格式化后的文本:”)
print(formatted_text_strict)

输出: (这次 Invalid format 的日期不会被替换)

Meeting scheduled for 2023/10/26.

The project started on 2022/04/15.

Deadline is 2024/01/31.

Another date: 2023/12/01.

Invalid format: 2023/10/26 or 10-26-2023.

“`

这个例子展示了如何结合使用不同的元字符、量词、分组、re.compilere.finditerre.sub 以及命名捕获组来解决实际问题。通过逐步构建模式和利用 re 模块提供的强大功能,你可以高效地处理各种复杂的文本匹配和操作任务。

总结

正则表达式是文本处理的瑞士军刀,而 Python 的 re 模块提供了这把刀的强大接口。掌握正则表达式需要时间和练习,但投入的努力绝对物有所值。

本文从正则表达式的基础概念(普通字符、元字符、特殊序列)讲起,强调了生字符串的重要性,详细介绍了 Python re 模块中的核心函数(search, match, findall, finditer, sub, split, compile),讲解了 Match 对象的使用,以及常用的标志和一些进阶技巧(非捕获组、命名组)和最佳实践。最后通过一个日期处理的综合案例展示了如何将这些知识融会贯通。

记住,学习正则表达式最好的方法就是动手实践。从简单的模式开始,不断尝试解决不同的文本处理问题,并利用在线工具辅助学习和调试。随着你对各种元字符和规则越来越熟悉,你会发现正则表达式不再神秘,而是成为了你处理文本数据的强大而灵活的工具。

希望本文能帮助你轻松踏入 Python 正则表达式的世界,并祝你在文本处理的道路上越走越顺!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部