Python 正则表达式详解 [特殊字符]

你是否曾想过如何在几百个文件中,快速找到所有格式为 xxx-xxxx-xxxx 的电话号码?或者从一堆乱七八糟的网页代码中,精准地提取出所有的链接和邮箱地址?

在 Python 中,我们主要通过内置的 re 模块来使用正则表达式。

参考文章:

Python 正则表达式 | 简单一点学习 easyeasy.me

Python 正则表达式高级应用 | 简单一点学习

1. 旅程的开始:re.search() 与第一个匹配

学习新东西,最好的方式就是直接上手。我们先来看最常用的一个函数:re.search()。它的作用是在一个字符串中搜索第一个符合规则的匹配项。

import re

# 要搜索的文本
text = "Hello, my number is 123-456-7890. Call me tomorrow!"
# 我们要找的“规则”(正则表达式)
pattern = "number"

# 开始搜索!
match = re.search(pattern, text)

# 如果找到了,match 就不是 None
if match:
    print("太棒了,找到了!")
    print(f"匹配到的内容是: '{match.group()}'")
    print(f"它从索引 {match.start()} 开始,到索引 {match.end()} 结束。")
else:
    print("唉,啥也没找到。")

在上面的例子里,我们的 pattern 只是一个普通的字符串 "number"re.search() 成功地在 text 中找到了它。

如果找到了匹配项,re.search() 会返回一个“匹配对象”(Match Object),我们可以从这个对象里获取很多有用的信息,比如匹配到的具体内容(.group())和它的位置(.start(), .end())。如果找不到,就返回 None

2. 解锁魔法:元字符(Metacharacters)

如果正则表达式只能查找固定文本,那就太无聊了。它的真正威力在于元字符——一些具有特殊含义的字符,它们能让你的匹配规则变得灵活又强大。

2.1. 匹配单个字符

元字符含义举例

.

匹配除换行符外的任意单个字符

h.t 可以匹配 hot, hat, h8t

\d

匹配任意一个数字 (0-9)

\d\d\d 可以匹配 123, 987

\D

匹配任意一个非数字

\D 可以匹配 a, !, (空格)

\w

匹配单词字符 (字母、数字、下划线)

\w\w\w 可以匹配 cat, d_g, ab1

\W

匹配非单词字符

\W 可以匹配 !, ?, (空格)

\s

匹配空白字符 (空格, \t, \n)

hello\sworld 可以匹配 hello world

\S

匹配非空白字符

\S 可以匹配 a, 1, !

看个例子,假设我们要找一个三位数的数字:

import re

text = "I have 3 apples, 10 oranges, and 999 pears."
pattern = r"\d\d\d"  # r"" 表示这是一个“原始字符串”,可以避免反斜杠的转义问题,写正则时强烈推荐使用!

match = re.search(pattern, text)
if match:
    print(f"找到了一个三位数: {match.group()}") # 输出: 找到了一个三位数: 999

2.2. 设定边界:锚点

锚点用来匹配字符串中的特定位置,而不是字符本身。

锚点含义

^

匹配字符串的开头

$

匹配字符串的结尾

\b

匹配一个单词边界(单词的开头或结尾)

\B

匹配一个非单词边界

\b 特别有用,可以帮你精确匹配整个单词。

import re

text = "This is a cat, not a catalog."
pattern1 = r"cat"
pattern2 = r"\bcat\b" # \b 表示 cat 这个词的左右两边都得是边界(比如空格或者句首/句尾)

print(f"用 '{pattern1}' 找到的: {re.search(pattern1, text).group()}") # 输出: cat
print(f"用 '{pattern2}' 找到的: {re.search(pattern2, text).group()}") # 输出: cat

# 在 "catalog" 中,'cat' 后面不是边界,所以 \bcat\b 匹配不到
text2 = "This is a catalog."
match = re.search(pattern2, text2)
if not match:
    print(f"在 '{text2}' 中找不到独立的单词 'cat'。")

2.3. 控制数量:量词

量词跟在某个字符或分组后面,用来指定它应该出现多少次。

量词含义

*

匹配前一个字符 0 次或多次

+

匹配前一个字符 1 次或多次

?

匹配前一个字符 0 次或 1 次

{m}

精确匹配 m 次

{m,n}

匹配 m 到 n 次

{m,}

至少匹配 m 次

现在,我们可以把前面的三位数例子写得更优雅:

import re

text = "I have 3 apples, 10 oranges, and 999 pears."
pattern = r"\d{3}"  # \d 出现 3 次

match = re.search(pattern, text)
print(f"用 \\d{{3}} 找到的三位数: {match.group()}") # 输出: 999

# 找一个或多个数字
pattern2 = r"\d+"
# re.findall() 是一个很酷的函数,可以找出所有匹配项,我们后面会详细说
matches = re.findall(pattern2, text)
print(f"用 \\d+ 找到的所有数字: {matches}") # 输出: ['3', '10', '999']

贪婪 vs. 非贪婪(懒惰)

默认情况下,量词是贪婪的,它们会尽可能多地匹配字符。比如 .* 会一直匹配到字符串的末尾。

如果你想让它尽可能少地匹配,可以在量词后面加个 ?,把它变成非贪婪(或称懒惰)模式。

import re

text = "<div>content1</div><div>content2</div>"

# 贪婪模式
greedy_pattern = r"<div>.*</div>"
print(f"贪婪模式: {re.search(greedy_pattern, text).group()}")
# 输出: <div>content1</div><div>content2</div> (从第一个 <div> 匹配到了最后一个 </div>)

# 非贪婪模式
lazy_pattern = r"<div>.*?</div>"
print(f"非贪婪模式: {re.search(lazy_pattern, text).group()}")
# 输出: <div>content1</div> (遇到第一个 </div> 就停止了)

3. 组合与选择:分组和分支

3.1. 字符集 []

如果你想匹配的不是某个特定字符,而是“某个范围内的任意一个字符”,可以用方括号 []

  • [abc]:匹配 abc 中的任意一个。

  • [0-9]:匹配任意一个数字,等同于 \d

  • [a-zA-Z]:匹配任意一个英文字母。

  • [^abc]:在方括号里用 ^ 开头,表示取反,即匹配除了 abc 之外的任意字符。

import re

text = "I like cats, but she likes dogs."
pattern = r"[cd]ats" # 匹配 'cats' 或者 'dats' (这里没有dats) -> 这里应该是 [cd]ogs

# 修正一下,匹配 cats 或 dogs
pattern_correct = r"\b[cd]ogs\b" # 噢,还是不对,应该是匹配 'cats' 或 'dogs'
pattern_final = r"\b(cats|dogs)\b" # 用 | 更合适,但我们先用字符集演示

# 演示字符集
text_vowel = "how are you?"
pattern_vowel = r"[aeiou]"
print(f"文本中的元音字母: {re.findall(pattern_vowel, text_vowel)}")
# 输出: ['o', 'a', 'e', 'o', 'u']

3.2. 分组 ()

用圆括号 () 可以将一部分正则表达式作为一个整体来对待,这被称为分组。分组有两大作用:

  1. 对分组使用量词:比如 (ab)+ 会匹配 abababababab 等。

  2. 捕获内容:被括号括起来的部分,其匹配到的内容会被“捕获”下来,方便我们后续单独提取。

import re

text = "My phone is 123-456-7890."
# 我们想把区号、前缀、后缀分开提取
pattern = r"(\d{3})-(\d{3})-(\d{4})"

match = re.search(pattern, text)
if match:
    print(f"完整匹配: {match.group(0)}") # group(0) 或 group() 总是完整的匹配
    print(f"区号: {match.group(1)}")     # 第 1 个括号捕获的内容
    print(f"前缀: {match.group(2)}")     # 第 2 个括号捕获的内容
    print(f"后缀: {match.group(3)}")     # 第 3 个括号捕获的内容

    # .groups() 可以一次性获取所有捕获组
    print(f"所有捕获组: {match.groups()}") # 输出: ('123', '456', '7890')

3.3. 分支 |

| 就像是编程里的 or,表示“或”的关系,匹配 | 左边或右边的表达式。

import re

text = "I love cats. I love dogs."
pattern = r"cats|dogs"

print(f"找到的动物: {re.findall(pattern, text)}") # 输出: ['cats', 'dogs']

4. re 模块的常用函数

我们已经见过 re.search()re.findall() 了,re 模块还有其他几个很能打的干将。

函数描述

re.match(pattern, string)

从字符串的开头开始匹配。如果开头不匹配,就失败了,返回 None

re.search(pattern, string)

扫描整个字符串,找到第一个匹配项。

re.findall(pattern, string)

找到所有匹配项,并以列表形式返回。

re.finditer(pattern, string)

findall 类似,但返回一个迭代器,每个元素是匹配对象,更省内存。

re.sub(pattern, repl, string)

替换。找到所有匹配 pattern 的子串,用 repl 替换它们。

re.split(pattern, string)

使用 pattern 作为分隔符来分割字符串。

re.compile(pattern)

编译一个正则表达式,如果一个正则要被反复使用,先编译可以提高效率。

match vs search

import re

text = "user@example.com"
pattern = r"example"

# search 可以在任何位置找到
print(f"search: {re.search(pattern, text)}") # <re.Match object ...>

# match 必须从头开始,所以这里找不到
print(f"match: {re.match(pattern, text)}")   # None

sub 的威力

re.sub 是一个非常强大的替换工具。

import re

text = "Agent Alice gave the secret documents to Agent Bob."
# 把所有 Agent XXX 的名字替换成 [REDACTED]
new_text = re.sub(r"Agent \w+", "[REDACTED]", text)
print(new_text)
# 输出: [REDACTED] gave the secret documents to [REDACTED].

5. 编译与标志位 (Flags)

re.compile()

当你需要多次使用同一个正则表达式时,先用 re.compile() 把它编译成一个模式对象,可以让程序运行得更快。

import re

text = "I have 1 apple, 2 bananas, 3 cherries."
# 编译正则表达式
p = re.compile(r"\d+")

# 多次使用这个编译好的模式
print(p.findall(text)) # ['1', '2', '3']
print(p.search(text))  # <re.Match object ...>

标志位 (Flags)

标志位可以改变正则表达式的匹配行为。

标志缩写含义

re.IGNORECASE

re.I

忽略大小写

re.MULTILINE

re.M

多行模式,让 ^$ 也能匹配每行的开头和结尾

re.DOTALL

re.S

. 可以匹配包括换行符在内的所有字符

import re

text = "Python is fun.\npython is powerful."

# 1. 不加 flag,默认区分大小写
print(re.findall(r"python", text)) # ['python']

# 2. 加上 re.I,忽略大小写
print(re.findall(r"python", text, re.IGNORECASE)) # ['Python', 'python']

# 3. 多行模式
text_multi = "hello world\nhello python"
# ^ 默认只匹配字符串开头
print(re.findall(r"^hello", text_multi)) # ['hello']
# re.M 让 ^ 也匹配换行后的开头
print(re.findall(r"^hello", text_multi, re.MULTILINE)) # ['hello', 'hello']

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值