正则表达式(Regular Expression)是一种强大的文本处理工具,用于匹配、查找、替换或提取符合特定模式的字符串。在Python中,我们使用内置的re
模块来实现正则表达式的功能:
目录
一、正则表达式的匹配过程
正则表达式的匹配过程可以简化为以下几个关键步骤:
- 编译:将用户编写的正则模式字符串转换为内部可执行结构(如自动机)。
- 初始化:在目标字符串上设定初始匹配位置。
- 状态转移:遍历目标字符串的字符。根据当前内部状态和字符,按自动机规则移动到下一个状态。
- 处理量词:对于重复次数不确定的子表达式(如*、+、?、{m,n}),尝试不同次数的匹配。
- 捕获分组:记录括号内子表达式匹配到的子串。
- 断言:检查特定位置是否满足额外条件,不影响匹配结果但影响匹配过程。
- 判定结果:若能完整遍历目标字符串且到达接受状态,匹配成功。否则,匹配失败。
- 全局搜索(可选):寻找目标字符串中所有匹配项,重复上述过程。
简单来说,正则表达式匹配就是将模式字符串编译为内部结构,然后在目标字符串上按照特定规则逐字符移动并处理量词、捕获分组、断言等,直至找到符合模式的子串或判定为不匹配。对于全局搜索,会在目标字符串中重复此过程以查找所有匹配项。
二、Python中使用正则表达式的基本步骤
1、导入re模块
import re
2、编译正则模式
使用re.compile()
函数将一个字符串形式的正则表达式编译成一个可重用的模式对象。这一步不是必需的,但有助于提高效率,尤其是在多次使用同一模式时。
pattern = re.compile(r'定义需要匹配的字符串')
3、应用模式
使用模式对象的方法来执行匹配操作,常见的方法有:
mach() | 从字符串起始位置尝试匹配 |
search() | 在整个字符串中搜索首次出现的匹配项 |
findall() | 返回所有非重叠的匹配项组成的列表 |
finditer() | 返回一个迭代器,生成所有非重叠匹配项的对象(每个对象包含匹配信息) |
fullmach() | 只有整个字符串完全匹配时才返回成功 |
split() | 根据模式分割字符串 |
sub() | 替换字符串中所有(或指定次数)的匹配项 |
如需了解更多请参阅re文档:re --- 正则表达式操作 --- Python
匹配对象实例也有几个方法和属性,最重要的是:
方法 / 属性 | 目的 |
| 返回正则匹配的字符串 |
| 返回匹配的开始位置 |
| 返回匹配的结束位置 |
| 返回包含匹配 (start, end) 位置的元组 |
三、正则表达式基础及进阶
1. 基本字符匹配
- 字母、数字、符号等直接匹配自身。
示例1:匹配字符串 "'hello,123456,text,text' 中的 "hello":
import re
str1='hello,123456,text,text'
ma=re.match('hello',str1)
print(ma)
#输出<re.Match object; span=(0, 5), match='hello'>
示例2:匹配字符串 "'hello,123456,text,text' 中的 "123456":
search1=re.search('123456',str1)
print(search1)
#输出<re.Match object; span=(6, 12), match='123456'>
2. 特殊字符(元字符)
有些字符是特殊的 元字符(metacharacters),并不匹配自身。事实上,它们表示匹配一些非常规的内容,或者通过重复它们或改变它们的含义来影响正则的其他部分
元字符的完整列表:. ^ $ * + ? { } [ ] \ | ( ) 在本文章中会分别讲到这些元字符的作用
- .(点):匹配除换行符外的任何单个字符。
- ^:匹配字符串的开始。
- $:匹配字符串的结束。
- \ :反斜杠用来转义,常见的如下:
\d
匹配任何十进制数字,等价于字符类[0-9]
。\D
匹配任何非数字字符,等价于字符类[^0-9]
。\s
匹配任何空白字符,等价于字符类[ \t\n\r\f\v]
。\S
匹配任何非空白字符,等价于字符类[^ \t\n\r\f\v]
。\w
匹配任何字母与数字字符,等价于字符类[a-zA-Z0-9_]
。\W
匹配任何非字母与数字字符,等价于字符类[^a-zA-Z0-9_]
。
[ ] : 用于指定一个字符类,希望匹配的字符的一个集合。这些字符可以单独地列出,也可以用字符范围来表示(给出两个字符并用
'-'
分隔)。例如,[abc]
将匹配a
、b
、c
之中的任意一个字符;这与[a-c]
相同,后者使用一个范围来表达相同的字符集合。如果只想匹配小写字母,则正则表达式将是[a-z]
,只匹配大写字母则是[A-Z]。元字符 (除了
\
) 在字符类中是不起作用的。 例如,[ak47$]
将会匹配以下任一字符'a'
,'k'
,'4','7'
或'$'
;而'$'
通常是一个元字符,但在一个字符类中它的特殊性被消除了。我们也可以通过对集合 取反 来匹配字符类中未列出的字符。方法是把
'^'
放在字符类的最开头。 例如,[^5]
将匹配除'5'
之外的任何字符。 如果插入符出现在字符类的其他位置,则它没有特殊含义。 例如:[5^]
将匹配'5'
或'^'
。
示例:
import re
str2='m416,ak47$,我有5个5,abcde' #匹配[a-c]
search2=re.search('[a-c]',str2)
print(search2) #<re.Match object; span=(5, 6), match='a'>
findall1=re.findall('[ak47$]',str2) #匹配[ak47$]
print(findall1) #['a', 'k', '4', '7', '$']
findall2=re.findall('[^5]',str2) #匹配除了5的字符
print(findall2)
#['m', '4', '1', '6', ',', 'a', 'k', '4', '7', '$', ',', '我', '有', '个', ',', 'a', 'b', 'c', 'd', 'e']
str3='hello python!'
match1=re.match('^h.*!$',str3) #匹配h开头和!结尾的字符串
print(match1) #<re.Match object; span=(0, 13), match='hello python!'>
3. 量词
- ?:前一个字符或子表达式可出现0次或1次(可选)。
- *:前一个字符或子表达式可出现0次或多次(重复任意次)。(*贪婪 *?非贪婪)
- +:前一个字符或子表达式至少出现1次(重复1次或多次)。
- {m,n}:前一个字符或子表达式至少出现m次,至多出现n次。
示例:
import re
test_string = 'banaanaaa'
# 匹配连续零个或一个'a'
pattern=re.compile('a?')
result = pattern.findall(test_string)
print(result) #['', 'a', '', 'a', 'a', '', 'a', 'a', 'a', ''] 'a'可以被空字符串、单个 'a'匹配
# 匹配连续零个或多个'a'
pattern1 = re.compile('a*')
result1 = pattern1.findall(test_string)
print(result1) #['', 'a', '', 'aa', '', 'aaa', ''] 'a'可以被空字符串、单个 'a'、两个 'a' 和三个 'a' 匹配
# 匹配至少一个'a'
pattern2 = re.compile('a+')
result2 = pattern2.findall(test_string)
print(result2) #['a', 'aa', 'aaa'] 'a'至少出现一次
# 匹配至少2次,至多3次'a'
pattern3 = re.compile('a{2,3}')
result3 = pattern3.findall(test_string)
print(result3) #['aa', 'aaa'] 'a'至少出现2次,至多出现3次
非贪婪模式和贪婪模式示例:
import re
html_text = "<div>This is some content</div><div>More content here</div>"
greedy_pattern = r"<div>(.*)</div>" # 注意此处的 .*
greedy_matches = re.findall(greedy_pattern, html_text)
print(greedy_matches) #['This is some content</div><div>More content here']
no_greedy_pattern = r"<div>(.*?)</div>" # 注意此处的 .*?
no_greedy_matches = re.findall(no_greedy_pattern, html_text)
print(no_greedy_matches) #['This is some content', 'More content here']
在这个例子中,非贪婪量词 .*? 在贪婪模式下会尽可能少地匹配内容,因此会分别匹配到每个 <div> 标签内的内容。而如果使用贪婪量词 .*,则只会匹配到整个字符串中第一个 <div> 和最后一个 </div> 之间的所有内容。
4.边界匹配与分组
- \b:匹配单词边界。
- ():用于创建捕获组,捕获匹配的内容,后续可通过\数字引用。
示例:
import re
#提取URL中的域名部分:
url = 'http://www.example.com/path/to/file.html'
pattern = re.compile(r'http://(.*?)/')
match = pattern.search(url)
print(match.group(1)) #获取第一个括号内的捕获组的内容,也就是我们想要提取的域名部分
#输出:www.example.com
#下面是一个使用\b和捕获组 的正则表达式示例,用于匹配电子邮件地址:
pattern=re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b')
test_string = 'Please contact me at my.email@example.com or another_email@sub.example.co.uk.'
emails = pattern.findall(test_string)
print(emails) #['my.email@example.com', 'another_email@sub.example.co.uk']
'''
在此正则表达式中:
\b 用于匹配单词边界,确保我们不会在单词内部匹配到邮箱地址。
([A-Za-z0-9._%+-]+) 是第一个捕获组,用于匹配用户名部分,其中可以包含字母、数字、点、下划线、百分号、加号和减号。
@ 是固定邮箱地址中的符号。
[A-Za-z0-9.-]+ 匹配域名部分的标签,允许字母、数字、点和短横线。
\. 匹配点号,它是域名的一部分。
[A-Z|a-z]{2,} 匹配顶级域名(TLD),至少由两个字母组成。
'''
5.选择、连接与断言
- |:选择符,匹配左右任一表达式。
- x y:连接两个表达式,匹配连续的 x 和 y。
- (?=p):正向前瞻断言,要求 p 在当前位置之后但不计入匹配。
- (?!p):负向前瞻断言,要求 p 不在当前位置之后。
示例:
#选择符 | (匹配左右任一表达式):
text = "I prefer Python or Java"
pattern = 'Python|Java' # "Python|Java" 这里 | 用来匹配 "Python" 或 "Java"。
matches = re.findall(pattern, text)
print(matches) # ['Python', 'Java']
#连接两个表达式 x y(匹配连续的 x 和 y)
text = "The quick brown fox jumps over the lazy dog"
pattern = "quick brown"
matches = re.findall(pattern, text) # 匹配连续的 "quick" 和 "brown"
print(matches) # ['quick brown']
#正向前瞻断言 (?=p)(要求 p 在当前位置之后但不计入匹配):
text = "123abcABCabc123"
pattern = r"\d+(?=abc)" # 匹配数字,后面紧跟 "abc",但不包括 "abc"
matches = re.findall(pattern, text) #这里 (?=abc) 表示匹配的数字后面必须是 "abc",但匹配结果只包含数字。
print(matches) # ['123', '123']
#负向前瞻断言 (?!p)(要求 p 不在当前位置之后):
text = "end123start456end"
pattern = r"\d+(?!end)" # 匹配数字,后面不能紧跟 "end"
matches = re.findall(pattern, text) #这里 (?!end) 表示匹配的数字后面不能是 "end",所以只会匹配到 "start456" 中的 "456"
print(matches) # ['456']
四、使用re
模块修改字符串
- re.sub(replacement, string[, count=0]):返回通过替换 replacement 替换 string 中正则的最左边非重叠出现而获得的字符串。 如果未找到模式,则 string 将保持不变。可选参数 count 是要替换的模式最大的出现次数;count 必须是非负整数。 默认值 0 表示替换所有。
- re.split(string[, maxsplit=0]) :通过正则表达式的匹配拆分 字符串。 如果在正则中使用捕获括号,则它们的内容也将作为结果列表的一部分返回。 如果 maxsplit 非零,则最多执行 maxsplit 次拆分。
re.split示例:
import re
# 分割含有多种分隔符的字符串
text = "Apple, Banana; Cherry: Date"
# 使用 | 作为选择符,匹配逗号或分号或冒号
pattern = r"[,:;]"
# 使用正则表达式分割字符串
split_result = re.split(pattern, text)
print(split_result) # 输出:['Apple', ' Banana', ' Cherry', ' Date']
# 若需去除空白字符,可以进一步处理
split_result = [part.strip() for part in split_result if part]
print(split_result) # 输出:['Apple', 'Banana', 'Cherry', 'Date']
re.sub示例:
# 替换连续重复的字母为单个字母
text = "bananaaa cccat doggo eeeefish"
# 使用正则表达式匹配连续重复两次及以上的字母
pattern = r"(\w)\1+"
# 使用 lambda 函数将匹配到的重复字母替换为其本身的一个实例
replace= lambda m: m.group(1)
# 使用 re.sub() 方法执行替换操作
modified_text = re.sub(pattern, replace, text)
print(modified_text) # 输出:banana cat dogo efish
五、使用标志(Flags)
通过在调用正则函数时添加标志参数(如 re.IGNORECASE
、re.MULTILINE
等),可以改变正则表达式的匹配行为。
示例:忽略大小写进行匹配:
import re
text = 'The Quick Brown Fox Jumps Over The Lazy Dog.'
pattern = re.compile(r'the', re.IGNORECASE)
matches = pattern.findall(text)
print(matches) # 输出:['The', 'The']
希望该篇文章能帮助大家对Python正则表达式有初步的认识。实际使用时,可以根据具体需求组合运用这些元素,编写出复杂且精确的匹配规则。随着实践的深入,我们就会发现正则表达式在处理字符串数据时的强大之处。但是也不要纠着正则不放哟,总的来说,正则表达式在处理文本模式匹配、验证和提取等任务时具有显著优势,但在面对简单字符串操作、已有专用工具覆盖的场景、结构化数据处理以及复杂的文本分析时,可能并非最佳选择。在实际开发中,应根据具体需求权衡使用正则表达式与其他工具的利弊。