Python的re模块:正则表达式处理的魔法棒

发布于:2025-05-18 ⋅ 阅读:(23) ⋅ 点赞:(0)

Python的re模块:正则表达式处理的魔法棒

图片

对话实录

小白:(发愁)我想从一大段文本里找出所有邮箱地址,手动找太费劲了,有没有简便方法?

专家:(挥舞魔法棒)用 Python 的re模块,轻松实现文本的搜索、匹配与替换!不过,要想用好它,得先掌握正则表达式的基础知识。


正则表达式基础知识

正则表达式作为高级的文本模式匹配、抽取、和搜索。简单地说,正则表达式(简称为 regex)是一些由字符和特殊符号组成的字符串,按照设定的匹配逻辑能够匹配一系列有相似特征的字符串。

常用到到的表达式符号如下,将不同的符号组合为正则表达式。

符号

解释

示例

re1|re2

匹配正则表达式 re1 或者 re2

foo|bar

.

匹配任何字符(除了\n 之外)

b.b

^

匹配字符串起始部分

^Dear

$

匹配字符串终止部分

/bin/*sh$

*

匹配 0 次或者多次前面出现的正则表达式

[A-Za-z0-9]*

+

匹配 1 次或者多次前面出现的正则表达式

[a-z]+\.com

?

匹配 0 次或者 1 次前面出现的正则表达式

goo?

{N}

匹配 N 次前面出现的正则表达式

[0-9]{3}

{M,N}

匹配 M~N 次前面出现的正则表达式

[0-9]{5,9}

[…]

匹配来自字符集的任意单一字符

[aeiou]

[..x−y..]

匹配 x~y 范围中的任意单一字符

[0-9], [A-Za-z]

[^…]

不匹配此字符集中出现的任何一个字符,包括某一范围的字符(如果在此字符集中出现)

[^aeiou], [^A-Za-z0-9]

(*|+|?|{})?

用于匹配上面频繁出现/重复出现符号的非贪婪版本(*、+、?、{})

.*?[a-z]

(…)

匹配封闭的正则表达式,然后另存为子组

([0-9]{3})?,f(oo|

\d

匹配任何十进制数字,与[0-9]一致(\D 与\d 相反,不匹配任何非数值型的数字)

data\d+.txt

\w

匹配任何字母数字字符,与[A-Za-z0-9_]相同(\W 与之相反)

[A-Za-z_]\w+

\s

匹配任何空格字符,与[\n\t\r\v\f]相同(\S 与之相反)

of\sthe

\b

匹配任何单词边界(\B 与之相反)

\bThe\b

\N

匹配已保存的子组 N(参见上面的(…))

price: \16

\c

逐字匹配任何特殊字符 c(即,仅按照字面意义匹配,不匹配特殊含义)

\., \\, \*

\A(\Z)

匹配字符串的起始(结束)(另见上面介绍的^和$)

\ADear

举例:正则表达式中,普通字符会精确匹配自身。比如要匹配字符串 “hello” 中的 “h”,直接用 “h” 就能匹配。

import re
text = "hello"
match = re.search('h', text)
if match:
  print("匹配成功")

特殊字符在正则里有特定含义,不能直接匹配其字面意思。像\就是特殊字符,若要匹配反斜杠,需写成\\。

re模块基础入门

1 常用函数速查表

函数

用法

说明

re.match

re.match(pattern, string)

尝试从字符串的起始部分对模式进行匹配。如果匹配成功,返回一个匹配对象;如果匹配失败,返回 None; 匹配对象的 group()方法用于显示成功的匹配。

re.search

re.search(pattern, string)

search()方法会在任意位置搜索正则表达式第一次出现的匹配情况(即使可以匹配到多个,也只会获取第一次匹配到的数据)。如果搜索到成功的匹配,会返回一个匹配对象;否则,返回 None。

re.sub

re.sub(pattern, repl, string)

替换匹配到的文本

re.split

re.split(pattern, string)

split()函数在正则表达式匹配到内容后,将其他未匹配的内容分割为列表,可支持最大分割次数,类似与字符串str.split()方法。

re.findall

re.findall(pattern, string)

findall()函数匹配正则表达式,匹配所有符合条件的数据,并返回一个列表,匹配不上返回为空列表

re.finditer

re.finditer(pattern, string)

finditer()函数与 findall()函数相同,但返回的不是一个列表,而是一个迭代器。对于每一次匹配,迭代器都返回一个match匹配对象

re.compile

re.compile(pattern)

将正则表达式预编译成对象,后续使用该对象进行匹配。

2. 简单匹配

re模块中的search函数可在字符串中搜索匹配的模式。假设我们要在一段文本中查找 “apple”:

import re
text = "I have an apple and a banana."
match = re.search('apple', text)
if match:
   print("找到了!位置是从", match.start(), "到", match.end())
# 输出:找到了!位置是从 7 到 12

这里re.search就像在文本 “大海” 里捞 “apple” 这根 “针”,一旦找到,就能告诉你它的位置。

3. 使用正则表达式

正则表达式是re模块的核心,它能定义更复杂的匹配模式。比如匹配一个简单的电话号码格式(三位数字 - 四位数字):

phone_pattern = r'\d{3}-\d{4}'
phone_text = "联系电话:123-4567"
phone_match = re.search(phone_pattern, phone_text)
if phone_match:
  print("匹配到电话号码:", phone_match.group())
# 输出:匹配到电话号码: 123-4567

\d表示数字,{3}和{4}分别表示前面的字符(数字)重复 3 次和 4 次,这样就精准匹配到了目标电话号码格式。

常用功能及案例

案例 1:提取邮箱地址

实际应用中,从文本提取邮箱地址很常见。

email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
text_with_email = "我的邮箱是example@domain.com,有事联系。"
email_match = re.search(email_pattern, text_with_email)
if email_match:
  print("提取到的邮箱:", email_match.group())
# 输出:提取到的邮箱: example@domain.com

这个正则表达式通过\b单词边界确保匹配的是完整邮箱,[A-Za-z0-9._%+-]+匹配邮箱名部分,@后面匹配域名部分。

案例 2:替换文本

re模块的sub函数可用于替换匹配到的文本。比如将一段文本中的所有数字替换为 “X”:

text_with_nums = "我今年25岁,有3只猫。"
new_text = re.sub(r'\d+', 'X', text_with_nums)
print(new_text)
# 输出:我今年X岁,有X只猫。

\d+表示匹配一个或多个数字,re.sub将这些数字全部替换为 “X”。

案例 3:分割文本

re模块的split函数能按指定模式分割字符串。例如,按逗号和空格分割句子:

sentence = "苹果, 香蕉, 橙子, 葡萄"
fruits = re.split(r',\s*', sentence)
print(fruits)
# 输出:['苹果', '香蕉', '橙子', '葡萄']

r',\s*'这个模式表示匹配逗号及后面可能存在的 0 个或多个空格,以此来分割句子得到水果列表。

闭坑指南

正则表达式书写错误

正则表达式语法复杂,一个小错误就可能导致匹配失败。比如想匹配一个整数,错误地写成\d(只匹配一个数字),而正确的应该是\d+(匹配一个或多个数字)。

# 错误示范,只能匹配一个数字
num_pattern_wrong = r'\d'
num_text = "12345"
wrong_match = re.search(num_pattern_wrong, num_text)
if wrong_match:
  print("错误匹配:", wrong_match.group())
else:
  print("未匹配到完整数字")
# 输出:未匹配到完整数字

# 正确示范
num_pattern_right = r'\d+'
right_match = re.search(num_pattern_right, num_text)
if right_match:
  print("正确匹配:", right_match.group())
# 输出:正确匹配: 12345

务必仔细检查正则表达式语法,可借助在线正则表达式测试工具辅助调试。

贪婪与非贪婪模式混淆

正则表达式默认是贪婪模式,会尽可能多地匹配。比如.*会匹配尽可能长的字符串。如果想匹配最短的字符串,要用非贪婪模式.*?。

html_text = "
内容1
内容2
"
# 贪婪模式,会匹配整个文本
greedy_match = re.search(r'
.*
', html_text)
if greedy_match:
  print("贪婪匹配:", greedy_match.group())
# 输出:贪婪匹配: 
内容1
内容2


# 非贪婪模式,只匹配第一个div内容
non_greedy_match = re.search(r'
.*?
', html_text)
if non_greedy_match:
  print("非贪婪匹配:", non_greedy_match.group())
# 输出:非贪婪匹配: 
内容1

忽略转义字符

在正则表达式中,一些字符有特殊含义,如\。如果要匹配这些特殊字符本身,需要进行转义。例如匹配路径中的反斜杠\,要写成\\。

path = "C:\Program Files\Python"
# 错误示范,未转义反斜杠,会导致语法错误
# path_pattern_wrong = r'C:\Program Files\Python'

# 正确示范,转义反斜杠
path_pattern_right = r'C:\\Program Files\\Python'
right_match = re.search(path_pattern_right, path)
if right_match:
  print("正确匹配路径:", right_match.group())

注意特殊字符的转义,确保正确匹配。

专家工具箱

1. 分组与捕获

使用括号()可以对正则表达式进行分组,捕获匹配的内容。比如从一个包含姓名和年龄的字符串中分别提取姓名和年龄:

info_pattern = r'(\w+)\s+(\d+)'
info_text = "Alice 25"
match = re.search(info_pattern, info_text)
if match:
  name = match.group(1)
  age = match.group(2)
print("姓名:", name, "年龄:", age)
# 输出:姓名: Alice 年龄: 25

这里(\w+)捕获姓名,(\d+)捕获年龄,方便后续分别处理。

2. 预编译正则表达式

对于需要多次使用的正则表达式,预编译可以提高效率。

email_pattern = re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b')
texts = ["邮箱1: test1@example.com", "邮箱2: test2@domain.net"]
for text in texts:
  match = email_pattern.search(text)
  if match:
    print("提取到邮箱:", match.group())

re.compile将正则表达式编译成对象,后续使用该对象进行匹配比每次都使用re.search效率更高。

3. 零宽断言

零宽断言用于在不匹配具体字符的情况下,断言某个位置满足特定条件。比如匹配以数字结尾的单词,但不捕获数字:

pattern = r'\w+(?=\d)'
text = "苹果1 香蕉2 橙子"
matches = re.findall(pattern, text)
print(matches)
# 输出:['苹果', '香蕉']

(?=\d)是正向零宽断言,表示前面的单词后面必须跟着一个数字,但数字不被匹配。

小白:(眼睛放光)原来re模块这么强大,能解决这么多文本处理难题!

专家:(微笑)记住:掌握re模块,文本处理将变得高效且精准!

图片


网站公告

今日签到

点亮在社区的每一天
去签到