Python 正则表达式入门 – wiki基地


Python 正则表达式入门:从零开始掌握强大的文本匹配工具

在处理字符串数据时,我们经常需要执行一些复杂的模式匹配、查找、替换或分割操作。例如,从一段文本中提取所有电话号码、验证用户输入的邮箱格式、或者查找所有以特定前缀开头的单词。手动编写代码来处理这些需求通常非常繁琐且容易出错。这时,正则表达式(Regular Expression,简称 Regex 或 RE)就派上了用场。

正则表达式是一种强大而灵活的文本模式匹配工具,它用简洁的符号描述了字符串的模式。Python 内建的 re 模块提供了对正则表达式的完整支持,使得在 Python 中进行复杂的字符串处理变得轻而易举。

本文将带你从零开始,逐步深入了解 Python 中正则表达式的使用,包括基本的语法、re 模块的核心函数以及如何处理匹配结果。

什么是正则表达式?

简单来说,正则表达式就是用来描述一组字符串特征的模式。它由一系列特殊字符和普通字符组成,这些字符组合起来定义了一个搜索模式。你可以把它想象成一个高级的通配符系统,但其能力远超通配符。

例如,如果你想匹配所有以字母 a 开头,后面跟着任意多个字母或数字的字符串,用正则表达式可以简洁地表示出来。

为什么在 Python 中使用正则表达式?

Python 的字符串处理能力本身就很强大,提供了 startswith(), endswith(), find(), replace(), split() 等方法。但当需求变得复杂时,比如:

  • 查找所有符合特定格式(如 XXX-XXXX-XXXX)的电话号码。
  • 从网页 HTML 中提取所有 URL 链接。
  • 验证用户输入的密码是否包含大小写字母、数字和特殊字符。
  • 从日志文件中过滤出特定错误信息。

这时,使用正则表达式会比使用 Python 内建字符串方法组合来实现要高效得多,代码也更简洁、更易读(一旦你熟悉了正则表达式的语法)。Python 的 re 模块提供了与 Perl 语言类似的正则表达式功能,非常成熟和稳定。

Python 的 re 模块

Python 使用标准的 re 模块来支持正则表达式。要使用正则表达式,首先需要导入这个模块:

python
import re

re 模块提供了一系列函数,用于执行正则表达式操作,如查找、匹配、替换、分割等。这些函数通常接收两个主要参数:

  1. Pattern (模式): 一个字符串,表示要匹配的正则表达式。
  2. String (字符串): 要在其中进行匹配操作的目标字符串。

原始字符串 (Raw Strings) 的重要性

在 Python 中书写正则表达式模式时,强烈推荐使用原始字符串(Raw Strings)。原始字符串以 rR 开头,例如 r"your regex pattern"

为什么需要原始字符串?因为正则表达式中大量使用了反斜杠 \ 作为转义字符(例如 \d 表示数字,\s 表示空白字符)。而 Python 字符串本身也使用反斜杠作为转义字符(例如 \n 表示换行,\t 表示制表符)。如果不使用原始字符串,你就需要对模式中的每个反斜杠都进行双重转义,比如 \\d 来表示正则表达式中的 \d。这会让模式变得非常难以阅读和书写。

使用原始字符串 r"\d+",Python 解释器会将其中的反斜杠视为字面量,直接传递给 re 模块,避免了 Python 自身的转义干扰。

“`python

不使用原始字符串,需要双重转义

pattern_without_raw = “\d+”
print(pattern_without_raw) # 输出:\d+ (Python内部表示)

使用原始字符串,推荐方式

pattern_with_raw = r”\d+”
print(pattern_with_raw) # 输出:\d+ (直接表示正则表达式中的 \d+)
“`

因此,记住:编写正则表达式模式时,总是使用原始字符串 r""

正则表达式基础语法 (Patterns)

正则表达式的强大之处在于其模式语法。掌握这些基本语法是使用正则表达式的关键。

1. 字面字符 (Literal Characters)

大多数字符(如字母、数字、符号)在正则表达式中都表示它们本身的含义。例如,模式 cat 会精确匹配字符串中的子串 “cat”。

“`python
import re

text = “The cat sat on the mat.”
pattern = r”cat”

match = re.search(pattern, text)
if match:
print(f”找到匹配: {match.group()}”) # 输出:找到匹配: cat
“`

2. 元字符 (Metacharacters)

元字符是正则表达式中具有特殊含义的字符,它们不是匹配字符本身,而是描述匹配的规则。这是正则表达式灵活性的核心。

  • . (点号): 匹配除了换行符 \n 之外的任意单个字符

    “`python
    text = “hat hot hit”
    pattern = r”h.t” # 匹配 h 后面跟任意一个字符,再跟 t

    matches = re.findall(pattern, text)
    print(matches) # 输出:[‘hat’, ‘hot’, ‘hit’]
    “`

  • ^ (脱字符): 匹配字符串的开头

    “`python
    text1 = “Hello world”
    text2 = “world Hello”
    pattern = r”^Hello” # 匹配以 Hello 开头的字符串

    match1 = re.search(pattern, text1)
    match2 = re.search(pattern, text2)

    print(f”‘{text1}’ 开头是 ‘Hello’吗? {bool(match1)}”) # 输出:’Hello world’ 开头是 ‘Hello’吗? True
    print(f”‘{text2}’ 开头是 ‘Hello’吗? {bool(match2)}”) # 输出:’world Hello’ 开头是 ‘Hello’吗? False
    “`

  • $ (美元符号): 匹配字符串的结尾

    “`python
    text1 = “Hello world”
    text2 = “world Hello”
    pattern = r”world$” # 匹配以 world 结尾的字符串

    match1 = re.search(pattern, text1)
    match2 = re.search(pattern, text2)

    print(f”‘{text1}’ 结尾是 ‘world’吗? {bool(match1)}”) # 输出:’Hello world’ 结尾是 ‘world’吗? True
    print(f”‘{text2}’ 结尾是 ‘world’吗? {bool(match2)}”) # 输出:’world Hello’ 结尾是 ‘world’吗? False
    “`

  • * (星号): 匹配前一个字符零次或多次

    “`python
    text = “go goo goooogle”
    pattern = r”go*” # 匹配 g 后面跟着零个或多个 o

    matches = re.findall(pattern, text)
    print(matches) # 输出:[‘g’, ‘goo’, ‘goooo’]
    “`

  • + (加号): 匹配前一个字符一次或多次

    “`python
    text = “go goo goooogle”
    pattern = r”go+” # 匹配 g 后面跟着一个或多个 o

    matches = re.findall(pattern, text)
    print(matches) # 输出:[‘goo’, ‘goooo’]
    “`

  • ? (问号): 匹配前一个字符零次或一次

    “`python
    text = “color colour”
    pattern = r”colou?r” # 匹配 colou 后面跟着零个或一个 u,再跟着 r

    matches = re.findall(pattern, text)
    print(matches) # 输出:[‘color’, ‘colour’]
    “`

3. 字符集/字符类 (Character Sets/Classes) []

方括号 [] 定义了一个字符集合。它匹配方括号中列出的任意一个字符

  • [abc] 匹配 ‘a’, ‘b’, 或 ‘c’ 中的任意一个。
  • [0-9] 匹配任意一个数字(等同于 \d)。
  • [a-z] 匹配任意一个小写字母。
  • [A-Z] 匹配任意一个大写字母。
  • [a-zA-Z] 匹配任意一个字母。
  • [a-zA-Z0-9] 匹配任意一个字母或数字(等同于 \w 的一部分)。

[] 内部,大部分元字符会失去特殊含义(除了 ^ 在开头表示否定,- 在中间表示范围,\ 进行转义,以及 ] 本身需要转义 \])。

“`python
text = “cat hat bat sat”
pattern = r”[chb]at” # 匹配 c, h, 或 b 中的任意一个,后面跟着 at

matches = re.findall(pattern, text)
print(matches) # 输出:[‘cat’, ‘hat’, ‘bat’]
“`

4. 否定字符集 [^]

[] 内部的开头使用 ^ 表示匹配不在该集合中的任意一个字符

“`python
text = “cat mat bat fat 1at”
pattern = r”[^cmf]at” # 匹配不是 c, m, 或 f 的任意一个字符,后面跟着 at

matches = re.findall(pattern, text)
print(matches) # 输出:[‘bat’, ‘1at’]
“`

5. 预定义字符类 (Predefined Character Classes)

re 模块提供了一些常用的预定义字符类,它们是常用字符集的简写形式,更方便。

  • \d: 匹配任意一个数字 (0-9)。等同于 [0-9]
  • \D: 匹配任意一个非数字字符。等同于 [^0-9]
  • \w: 匹配任意一个字母、数字或下划线。在 ASCII 模式下等同于 [a-zA-Z0-9_]
  • \W: 匹配任意一个非字母、数字或下划线字符。等同于 [^\w]
  • \s: 匹配任意一个空白字符(空格、制表符 \t、换行符 \n、回车符 \r、换页符 \f 等)。
  • \S: 匹配任意一个非空白字符。等同于 [^\s]

“`python
text = “Phone number: 123-456-7890, Email: [email protected]
pattern_number = r”\d+” # 匹配一个或多个数字
pattern_word = r”\w+” # 匹配一个或多个字母、数字或下划线
pattern_whitespace = r”\s+” # 匹配一个或多个空白字符

numbers = re.findall(pattern_number, text)
words = re.findall(pattern_word, text)
whitespaces = re.findall(pattern_whitespace, text)

print(f”Numbers: {numbers}”) # 输出:Numbers: [‘123’, ‘456’, ‘7890’]
print(f”Words: {words}”) # 输出:Words: [‘Phone’, ‘number’, ‘123’, ‘456’, ‘7890’, ‘Email’, ‘test’, ‘example’, ‘com’]
print(f”Whitespaces: {whitespaces}”) # 输出:Whitespaces: [‘ ‘, ‘: ‘, ‘-‘, ‘-‘, ‘, ‘, ‘ ‘, ‘: ‘, ‘@’, ‘.’]
“`

6. 量词 (Quantifiers)

量词用于指定前一个字符、字符类或分组应该出现多少次。

  • {n}: 匹配前一个元素恰好出现 n 次。
  • {n,}: 匹配前一个元素至少出现 n 次。
  • {,m}: 匹配前一个元素最多出现 m 次。
  • {n,m}: 匹配前一个元素出现 n 到 m 次(包含 n 和 m)。

注意: * 等同于 {0,}, + 等同于 {1,}, ? 等同于 {0,1}

默认情况下,量词是“贪婪”的(Greedy),会尽可能多地匹配字符。在量词后加上 ? 可以使其变为“非贪婪”或“惰性”的(Non-greedy/Lazy),会尽可能少地匹配字符。

“`python
text = “aaaaa”
pattern_greedy = r”a{2,4}” # 匹配 2 到 4 个 a (贪婪)
pattern_lazy = r”a{2,4}?” # 匹配 2 到 4 个 a (非贪婪)

match_greedy = re.search(pattern_greedy, text)
match_lazy = re.search(pattern_lazy, text)

print(f”Greedy match: {match_greedy.group()}”) # 输出:Greedy match: aaaa
print(f”Lazy match: {match_lazy.group()}”) # 输出:Lazy match: aa
“`

7. 边界匹配 (Anchors)

除了 ^$,还有其他用于匹配特定位置的元字符。

  • \b: 匹配一个单词边界。单词边界是指一个 \w 字符和一个 \W 字符之间的位置,或者 \w 字符与字符串开头/结尾之间的位置。它匹配的是位置,而不是字符本身。
  • \B: 匹配一个非单词边界

“`python
text = “cat catalog concatenate the scattered cats”
pattern_word = r”\bcat\b” # 匹配完整的单词 “cat”
pattern_not_word = r”\Bcat\B” # 匹配前后都不是单词边界的 “cat”

matches_word = re.findall(pattern_word, text)
matches_not_word = re.findall(pattern_not_word, text)

print(f”Full word ‘cat’: {matches_word}”) # 输出:Full word ‘cat’: [‘cat’, ‘cats’] (注意 cats 结尾是边界)
print(f”Non-word boundary ‘cat’: {matches_not_word}”) # 输出:Non-word boundary ‘cat’: [‘cat’] (在 concatenate 中)
“`

8. 分组 (Grouping) ()

圆括号 () 用于将一个或多个字符组合成一个逻辑单元,可以对整个组应用量词,或者用于捕获匹配的子串(后面会讲到)。

“`python
text = “ababab”
pattern = r”(ab)+” # 匹配一个或多个 “ab” 组合

matches = re.findall(pattern, text)
print(matches) # 输出:[‘ab’] – 注意 findall 在有分组时只返回捕获组的内容
``
要获取完整的匹配,需要结合使用或使用其他函数,或者理解
findall在有无分组时的区别。对于findall`,如果没有分组,它返回所有完全匹配的子串;如果有分组,它返回所有匹配的捕获组的内容(如果只有一个分组,返回列表,如果有多个分组,返回元组列表)。

9. 候选项 (Alternation) |

竖线 | 用于表示“或”的关系,匹配 | 符号左边或右边的模式。通常与分组 () 一起使用。

“`python
text = “apple banana orange”
pattern = r”(apple|banana)” # 匹配 “apple” 或 “banana”

matches = re.findall(pattern, text)
print(matches) # 输出:[‘apple’, ‘banana’]
“`

10. 转义特殊字符 \

如果想要匹配元字符本身(如 . * + ? ^ $ () [] {} | \),需要在前面加上反斜杠 \ 进行转义。

“`python
text = “Need 10$ for the game.”
pattern = r”10\$” # 匹配字面量 “10$”

match = re.search(pattern, text)
if match:
print(f”找到匹配: {match.group()}”) # 输出:找到匹配: 10$
“`
在使用原始字符串时,你只需要写一个反斜杠来转义元字符。

re 模块的核心函数

了解了正则表达式的模式语法后,我们来看看如何在 Python 中使用 re 模块的函数来执行匹配操作。

1. re.search(pattern, string, flags=0)

扫描整个字符串,查找第一个与模式匹配的位置。如果找到,返回一个Match Object (匹配对象);如果没有找到,返回 None

匹配对象包含了匹配的详细信息。

“`python
import re

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

match = re.search(pattern, text)

if match:
print(“找到匹配!”)
print(f”匹配的起始位置: {match.start()}”)
print(f”匹配的结束位置: {match.end()}”)
print(f”匹配的子串: {match.group()}”)
else:
print(“未找到匹配。”)

输出:

找到匹配!

匹配的起始位置: 16

匹配的结束位置: 19

匹配的子串: fox

“`

2. re.match(pattern, string, flags=0)

尝试从字符串的开头匹配模式。如果字符串的开头符合模式,返回一个 Match Object;否则,返回 None

match()search() 的主要区别在于 match() 只从字符串的起始位置开始匹配,而 search() 会扫描整个字符串直到找到第一个匹配。

“`python
import re

text1 = “Hello world”
text2 = “world Hello”
pattern = r”Hello”

match1 = re.match(pattern, text1)
match2 = re.match(pattern, text2)

print(f”re.match(‘{pattern}’, ‘{text1}’): {bool(match1)}”) # 输出:re.match(‘Hello’, ‘Hello world’): True
print(f”re.match(‘{pattern}’, ‘{text2}’): {bool(match2)}”) # 输出:re.match(‘Hello’, ‘world Hello’): False

print(f”re.search(‘{pattern}’, ‘{text2}’): {bool(re.search(pattern, text2))}”) # 输出:re.search(‘Hello’, ‘world Hello’): True
“`

3. re.findall(pattern, string, flags=0)

在字符串中查找所有与模式匹配的非重叠子串,并以一个列表的形式返回。

  • 如果模式中没有捕获组(即没有使用 () 分组),返回所有完全匹配的子串列表。
  • 如果模式中有一个捕获组,返回该捕获组匹配到的所有内容的列表。
  • 如果模式中有多个捕获组,返回一个元组列表,每个元组包含一个匹配中所有捕获组的内容。

“`python
import re

text = “The numbers are 123, 456 and 789.”
pattern_no_group = r”\d+”
pattern_with_group = r”(\d+)” # 使用了捕获组

matches_no_group = re.findall(pattern_no_group, text)
matches_with_group = re.findall(pattern_with_group, text)

print(f”findall (no group): {matches_no_group}”) # 输出:findall (no group): [‘123’, ‘456’, ‘789’]
print(f”findall (with group): {matches_with_group}”) # 输出:findall (with group): [‘123’, ‘456’, ‘789’]

text_dates = “Date: 2023-10-26, Another: 2024-01-15″
pattern_multi_group = r”(\d{4})-(\d{2})-(\d{2})”

matches_multi_group = re.findall(pattern_multi_group, text_dates)
print(f”findall (multi group): {matches_multi_group}”)

输出:findall (multi group): [(‘2023′, ’10’, ’26’), (‘2024′, ’01’, ’15’)]

“`

4. re.finditer(pattern, string, flags=0)

在字符串中查找所有与模式匹配的非重叠子串,并返回一个迭代器,该迭代器生成 Match Object。当需要获取每个匹配的详细信息(如位置、捕获组内容)时,finditer()findall() 更有用。

“`python
import re

text = “Emails: [email protected], [email protected]
pattern = r”(\w+)@(\w+.\w+)” # 捕获用户名和域名

for match in re.finditer(pattern, text):
print(f”完整匹配: {match.group()}”)
print(f”用户名: {match.group(1)}”) # group(1) 获取第一个捕获组的内容
print(f”域名: {match.group(2)}”) # group(2) 获取第二个捕获组的内容
print(f”匹配范围: {match.span()}”)
print(“-” * 10)

输出:

完整匹配: [email protected]

用户名: test1

域名: example.com

匹配范围: (8, 27)

———-

完整匹配: [email protected]

用户名: test2

域名: domain.org

匹配范围: (30, 48)

“`

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

在字符串中查找所有与模式匹配的子串,并用指定的替换字符串 repl 替换它们。返回替换后的新字符串。

  • repl 可以是一个字符串,也可以是一个函数。
  • count 参数指定最多替换的次数,默认为 0,表示替换所有匹配。

如果 repl 是一个字符串,可以使用 \g<number>\g<name>(对于命名分组)来引用捕获组的内容。

“`python
import re

text = “Date: 2023-10-26”

将 YYYY-MM-DD 格式转换为 MM/DD/YYYY

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

\g<2> 引用第二个捕获组(月份),\g<3> 引用第三个(日期),\g<1> 引用第一个(年份)

replacement = r”\g<2>/\g<3>/\g<1>”

new_text = re.sub(pattern, replacement, text)
print(f”Original: {text}”)
print(f”Replaced: {new_text}”)

输出:

Original: Date: 2023-10-26

Replaced: Date: 10/26/2023

“`

如果 repl 是一个函数,该函数会接收一个 Match Object 作为参数,并返回用于替换的字符串。这提供了更灵活的替换逻辑。

“`python
import re

text = “Numbers: 10, 25, 5″
pattern = r”\d+”

定义一个替换函数,将数字乘以2

def double_number(match):
number = int(match.group(0)) # match.group(0) 或 match.group() 获取整个匹配的子串
return str(number * 2)

new_text = re.sub(pattern, double_number, text)
print(f”Original: {text}”)
print(f”Replaced: {new_text}”)

输出:

Original: Numbers: 10, 25, 5

Replaced: Numbers: 20, 50, 10

“`

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

根据正则表达式模式在字符串中进行分割。返回一个字符串列表。

  • maxsplit 参数指定最大分割次数,默认为 0,表示所有匹配位置都进行分割。
  • 如果模式中有捕获组,那么捕获组匹配到的内容也会包含在结果列表中。

“`python
import re

text = “apple,banana;orange grape”
pattern_delimiters = r”[;, ]” # 匹配逗号、分号或空格作为分隔符

parts = re.split(pattern_delimiters, text)
print(f”Split by [,; ]: {parts}”) # 输出:Split by [,; ]: [‘apple’, ‘banana’, ‘orange’, ‘grape’]

如果分隔符有捕获组

pattern_delimiters_with_group = r”([;, ])”
parts_with_group = re.split(pattern_delimiters_with_group, text)
print(f”Split by ([;, ]): {parts_with_group}”)

输出:Split by ([;, ]): [‘apple’, ‘,’, ‘banana’, ‘;’, ‘orange’, ‘ ‘, ‘grape’, ”]

“`

7. re.compile(pattern, flags=0)

将正则表达式模式编译成一个正则表达式对象。编译后的对象具有与 re 模块函数同名的方法(如 search, match, findall, sub, split 等)。

编译正则表达式有两个主要好处:

  • 效率: 如果一个正则表达式需要在同一个程序中被多次使用,编译它可以显著提高效率,因为编译过程只需要执行一次。
  • 可读性: 将正则表达式对象存储在一个变量中,可以使代码更清晰。

“`python
import re

编译正则表达式对象

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

text1 = “Contact: [email protected]
text2 = “Reply to: [email protected]

使用编译后的对象的方法

match1 = email_pattern.search(text1)
match2 = email_pattern.search(text2)

if match1:
print(f”Text 1 match: {match1.group()}”)
if match2:
print(f”Text 2 match: {match2.group()}”)

输出:

Text 1 match: [email protected]

Text 2 match: [email protected]

“`

匹配对象 (Match Object)

re.search()re.match() 找到匹配时,它们会返回一个 Match Object。这个对象包含了匹配的详细信息,可以通过其方法来访问:

  • match.group(0)match.group(): 返回整个匹配的子串。
  • match.group(n): 返回第 n 个捕获组匹配到的子串(从 1 开始计数)。
  • match.groups(): 返回一个元组,包含所有捕获组匹配到的子串。
  • match.start(n): 返回第 n 个捕获组(或整个匹配,如果 n=0)的起始索引。
  • match.end(n): 返回第 n 个捕获组(或整个匹配)的结束索引(不包含该位置)。
  • match.span(n): 返回第 n 个捕获组(或整个匹配)的起始和结束索引组成的元组 (start, end)

“`python
import re

text = “My number is 123-456-7890.”
pattern = r”(\d{3})-(\d{3})-(\d{4})” # 3个捕获组

match = re.search(pattern, text)

if match:
print(f”完整匹配: {match.group()}”) # 输出:完整匹配: 123-456-7890
print(f”第一个分组: {match.group(1)}”) # 输出:第一个分组: 123
print(f”第二个分组: {match.group(2)}”) # 输出:第二个分组: 456
print(f”第三个分组: {match.group(3)}”) # 输出:第三个分组: 7890
print(f”所有分组: {match.groups()}”) # 输出:所有分组: (‘123’, ‘456’, ‘7890’)
print(f”完整匹配的起始结束索引: {match.span()}”) # 输出:完整匹配的起始结束索引: (13, 25)
print(f”第一个分组的起始结束索引: {match.span(1)}”) # 输出:第一个分组的起始结束索引: (13, 16)
“`

正则表达式标志 (Flags)

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

  • re.IGNORECASEre.I: 忽略大小写匹配。
  • re.MULTILINEre.M: 多行模式。^$ 不仅匹配整个字符串的开头和结尾,还匹配每一行的开头和结尾(由换行符分隔)。
  • re.DOTALLre.S: 点号 . 匹配包括换行符 \n 在内的任意字符。
  • re.VERBOSEre.X: 详细模式。忽略模式字符串中的空白字符和 # 后面的注释,使复杂的正则表达式更易读。

可以通过位运算符 | 来组合多个标志。

“`python
import re

text = “Hello World\nhello python”

忽略大小写

match_ignorecase = re.search(r”^hello”, text, re.IGNORECASE)
print(f”忽略大小写匹配开头: {bool(match_ignorecase)}”) # 输出:忽略大小写匹配开头: True

多行模式

match_multiline = re.search(r”^hello”, text, re.MULTILINE) # 匹配每一行的开头
print(f”多行模式匹配第二个 ‘hello’: {bool(match_multiline)}”) # 输出:多行模式匹配第二个 ‘hello’: True

点号匹配换行符

text_multiline = “Line1\nLine2″
match_dot_no_flag = re.search(r”Line.\nLine.”, text_multiline) # 默认点号不匹配换行
match_dot_flag = re.search(r”Line.\nLine.”, text_multiline, re.DOTALL) # 点号匹配换行

print(f”默认点号匹配换行符? {bool(match_dot_no_flag)}”) # 输出:默认点号匹配换行符? False
print(f”DOTALL点号匹配换行符? {bool(match_dot_flag)}”) # 输出:DOTALL点号匹配换行符? True

详细模式

pattern_verbose = r”””
^(\d{4}) # 匹配年份
– # 匹配短横线
(\d{2}) # 匹配月份
– # 匹配短横线
(\d{2}) # 匹配日期
$ # 匹配结尾
“””
text_date = “2023-10-26″
match_verbose = re.search(pattern_verbose, text_date, re.VERBOSE)
print(f”详细模式匹配日期: {bool(match_verbose)}”) # 输出:详细模式匹配日期: True
“`

更多高级概念(初学者了解即可)

  • 非捕获分组 (?:...): 使用 (?:...) 可以分组但不创建捕获组,这在只需要分组进行量词限定或使用 | 时很有用,可以提高一点效率并避免在 groups() 中出现不必要的元素。
  • 前向断言和后向断言 ((?=...), (?!...), (?<=...), (?<!...)): 这些是零宽度的断言,它们匹配一个位置,该位置后面或前面跟着(或不跟着)特定的模式,但它们本身不消耗字符。
    • (?=...) 前向肯定断言:要求当前位置后面必须匹配 ...
    • (?!...) 前向否定断言:要求当前位置后面不能匹配 ...
    • (?<=...) 后向肯定断言:要求当前位置前面必须匹配 ...
    • (?<!...) 后向否定断言:要求当前位置前面不能匹配 ...
  • 命名分组 (?P<name>...): 使用 (?P<name>...) 可以给捕获组命名,通过 match.group('name')match.group(name) 来访问,增强代码可读性。在 re.sub 的替换字符串中可以使用 \g<name> 引用。

“`python
import re

text = “cat dog cat bat”

查找后面跟着空格或边界的 “cat”

pattern_lookahead = r”cat(?=\s|\b)”
matches_lookahead = re.findall(pattern_lookahead, text)
print(f”前向肯定断言示例: {matches_lookahead}”) # 输出:前向肯定断言示例: [‘cat’, ‘cat’]

使用命名分组

text_date = “Date is 2023-10-26”
pattern_named_group = r”(?P\d{4})-(?P\d{2})-(?P\d{2})”
match_named = re.search(pattern_named_group, text_date)
if match_named:
print(f”命名分组示例: 年={match_named.group(‘year’)}, 月={match_named.group(‘month’)}, 日={match_named.group(‘day’)}”)
# 输出:命名分组示例: 年=2023, 月=10, 日=26

# re.sub中使用命名分组
replacement_named = r"月\g<month>日\g<day>年\g<year>"
new_text_named = re.sub(pattern_named_group, replacement_named, text_date)
print(f"使用命名分组替换: {new_text_named}")
# 输出:使用命名分组替换: Date is 月10日26年2023

“`

实践中的建议

  1. 总是使用原始字符串 r""
  2. 对于重复使用的模式,考虑使用 re.compile()
  3. 理解 search()match() 的区别。 大多数情况下你可能需要的是 search()
  4. 理解 findall() 在有无分组时的不同行为。 如果需要详细匹配信息,使用 finditer()
  5. 从小处着手构建模式。 先匹配简单的部分,然后逐步添加更复杂的规则。
  6. 使用在线正则表达式测试工具。 这能极大地帮助你构建和调试复杂的模式。例如 regex101.com 或 regexr.com。
  7. 注释复杂的模式。 对于复杂的模式,使用 re.VERBOSE 标志并在模式中添加注释可以显著提高可读性。

总结

正则表达式是一个强大而富有表现力的工具,用于处理各种字符串匹配和操作任务。Python 的 re 模块提供了完整的功能支持。通过本文的学习,你应该已经掌握了正则表达式的基础语法,包括字面字符、元字符、字符集、量词、边界以及分组等,并了解了 re 模块中常用的函数:search(), match(), findall(), finditer(), sub(), split() 以及 compile()。同时,你也了解了如何处理匹配对象来获取详细信息,以及如何使用标志来修改匹配行为。

学习正则表达式需要时间和实践。最好的方法是多写、多练、多尝试。从简单的模式开始,逐步挑战更复杂的匹配问题。随着你对正则表达式语法的熟悉,你会发现它在文本处理领域能发挥巨大的作用,极大地提高你的编程效率。

现在,开始你的正则表达式之旅吧!


发表评论

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

滚动至顶部