正则表达式

Python中正则表达式的应用非常广泛,如数据挖掘、数据分析、网络爬虫、输入有效性验证等。


正则表达式(Regular Expression,在代码中简写为regex、regexp、RE或re)是预先定义好的一个"规则字符串",通过这个"规则字符串"可以匹配、查找和替换那些符合"规则"的文本。可以把它当成增强版的通配符。

虽然文本的查找和替换功能可通过字符串提供的方法实现,但是实现起来极为困难,且运算效率比较低。而使用正则表达式实现这些功能会比较简单,且效率很高,唯一困难之处在于编写合适的正则表达式。Python提供了利用正则表达式实现文本的匹配、查找和替换等操作的 re 模块。

正则表达式中的字符

正则表达式是一种字符串,是由普通字符和元字符(Metacharacters)组成的。

(1)普通字符

普通字符是按照字符字面意义表示的字符,如CSDN - 专业开发者社区http://www.baidu.com

(2)元字符

元字符是预先定义好的一些特殊字符串,如:\w 和 \.

元字符

元字符是用来描述其他字符的特殊字符,它由基本元字符和普通字符构成。基本元字符是构成元字符的组成要素。学习正则表达式某种意义上就是在学习元字符的使用。

字符 说明
\ 转义符,表示转义
. 表示任意一个字符(但不包含换行符)
+ 表示重复一次或多次
* 表示重复零次或多次
表示重复零次或一次
| 选择符号,表示"或关系",如:A | B 表示匹配A或B
{} 定义量词
() 定义分组
^ 可以表示取反或匹配一行的开始
$ 匹配一行的结束

字符转义

反斜杠 " \ " 不仅可以对普通字符进行转义,还可以对基本元字符进行转义。如" . "表示任意一个字符,而" \. "表示普通字符点。

开始与结束字符

基本元字符 ^ 和 ,它们可以用于匹配一行字符串的开始和结束。当正则表达式以"\^"开始时,则从字符串的开始位置匹配;当正则表达式以""结束时,则从字符串的结束位置匹配。

注意:由于正则表达式中经常会有反斜杠" \ "等特殊字符,如果采用普通字符串表示需要转义。而在字符串前面加上 r 则不需要转义,因此一般正则表达式采用原始字符串表示。

示例代码如下

import re

p1 = r'\w+@zhangsan\.com' # 匹配包含 @zhangsan.com 的任何字符串中的任何位置
p2 = r'^\w+@zhangsan\.com$' # 要求整个字符串必须是有效的电子邮件地址,并且只匹配完整的字符串
text = "Tony's email is tony_guan588@zhangsan.com"
m = re.search(p1,text)
#re.search 函数在 text 中搜索与 p1 匹配的模式
# 如果找到匹配项,它将返回一个匹配对象;如果没有找到
# 它将返回 None。然后打印这个匹配对象
print(m)

m1 = re.search(p2,text)
print(m)
email1 = 'tony_guan588@zhangsan.com'
m2 = re.search(p2,email1)
print(m2)

代码运行结果

<re.Match object; span=(16, 41), match='tony_guan588@zhangsan.com'>
<re.Match object; span=(16, 41), match='tony_guan588@zhangsan.com'>
<re.Match object; span=(0, 25), match='tony_guan588@zhangsan.com'>

字符类

正则表达式可以使用字符类(Character class),一个字符类定义一组字符的集合,其中的任一字符出现在输入字符串中即匹配成功。注意每次匹配只能匹配字符类中的一个字符。

定义字符类

定义一个普通的字符类需要使用"["和"]"元字符类。例如想要在输入字符串中匹配Python或python,可以使用正则表达式[Pp]ython。

示例代码如下

import re

p = r'[Pp]ython'

m = re.search(p,'I like python')
print(m)
m1 = re.search(p,'I like Python')
print(m1)
m2 = re.search(p,'I like Java')
print(m2)

代码运行结果

<re.Match object; span=(7, 13), match='python'>
<re.Match object; span=(7, 13), match='Python'>
None

字符类取反

在正则表达式中指定不想出现的字符,可以在字符前加"^"符号

示例代码如下

import re

p = r'[^1234567890]' # 表示输入字符串中非0123456789即匹配

m = re.search(p,'10000')
print(m)
m1 = re.search(p,'I like Python')
print(m1)

代码运行结果

None
<re.Match object; span=(0, 1), match='I'>

区间

区间使用连字符"-"表示的,例如[0-9]等同于[0123456789];也可以表示多个不用区间,如[0-9a-zA-Z]表示所有的数字和字母字符类。

示例代码如下

import re

p = r'[0-9a-zA-Z]' # 表示输入字符串中非0123456789即匹配

m = re.search(p,'_10000')
print(m)
m1 = re.search(p,'I like Python')
print(m1)

代码运行结果

<re.Match object; span=(1, 2), match='1'>
<re.Match object; span=(0, 1), match='I'>

预定义字符类

字符 说明
. 匹配任意一个字符
\\ 匹配反斜杠 \ 字符
\n 匹配换行
\r 匹配回车
\f 匹配一个换页符
\t 匹配一个水平制表符
\v 匹配一个垂直制表符
\s 匹配一个空格符,等价于[\t\n\r\f\v]
\S 匹配一个非空格符,等价于[^\s]
\d 匹配一个数字字符,等价于[0-9]
\D 匹配一个非数字字符,等价于[^0-9]
\w 匹配任何语言的单词字符、数字和下划线"_"等字符
\W 等价于[^\w]

量词

前面提到的正则表达式元字符只能匹配显示一次字符或字符串,如果想匹配显示多次字符或字符串可以使用量词,量词表示字符或字符串重复的次数。

使用量词

字符 说明
出现零次或一次
* 出现零次或多次
+ 出现一次或多次
{n} 出现 n 次
{n,m} 至少出现 n 次但不超过 m 次
{n,} 至少出现 n 次

示例代码如下

import re

m = re.search(r'\d?','654321') # 出现数字一次
print(m)

m = re.search(r'\d?','ABC') # 出现数字0次
print(m)

m = re.search(r'\d*','654321') # 出现数字多次
print(m)

m = re.search(r'\d+','654321') # 出现数字多次
print(m)

m = re.search(r'\d+','ABC') # 不匹配
print(m)

m = re.search(r'\d{8}','87654321') # 出现数字8次
print(m)

m = re.search(r'\d{8}','ABC') # 不匹配
print(m)

m = re.search(r'\d{9,}','87654321') # 不匹配
print(m)

代码运行结果

<re.Match object; span=(0, 1), match='6'>
<re.Match object; span=(0, 0), match=''>
<re.Match object; span=(0, 6), match='654321'>
<re.Match object; span=(0, 6), match='654321'>
None
<re.Match object; span=(0, 8), match='87654321'>
None
None

Process finished with exit code 0

贪婪量词和懒惰量词

贪婪量词会尽可能多地匹配字符,懒惰量词会尽可能少地匹配字符。Python 中的正则表达式量词默认是贪婪的,要想使用懒惰量词在后面加"?"即可。

示例代码如下

import re

# 贪婪量词,会匹配数字 8 次
m = re.search(r'\d{5,8}','87654321') # 出现数字8次
print(m)
# 懒惰量词,会匹配数字 5 次
m = re.search(r'\d{5,8}?','87654321') # 出现数字8次
print(m)

代码运行结果

<re.Match object; span=(0, 8), match='87654321'>
<re.Match object; span=(0, 5), match='87654'>

分组

如果想让一个字符串作为整体使用量词,则需要对整体字符串进行分组,也称子表达式。

定义分组

定义正则表达式分组,则需要将字符串放到一对小括号中。

示例代码如下

import re

p = r'(121){2}'
m = re.search(p,'121121abcabc')
print(m)
print(m.group()) # 返回匹配字符串
print(m.group(1)) # 获得第一组内容

p = r'(\d{3,4}-\d{7,8})'
m = re.search(p, '010-87654321')
print(m)
print(m.group()) # 返回匹配字符串
print(m.groups()) # 获得所有组内容

代码运行结果

<re.Match object; span=(0, 6), match='121121'>
121121
121
<re.Match object; span=(0, 12), match='010-87654321'>
010-87654321
('010-87654321',)

Process finished with exit code 0

其中上面代码调用 match 对象的 group() 方法返回匹配的字符串,group() 方法语法如下:

match.group([1,...])
其中 1 代表组编号,在正则表达式中组编号是从 1 开始的

命名分组

在 Python 中访问分组时,除了可以通过组编号进行访问,还可以通过组名进行访问,前提是要在正则表达式中为组命名,组命名通过在组开头添加"?P<组名>"实现。当在程序中访问这些带有名字的组时,可以通过组编号或组名字进行访问。

反向引用分组

除了可以在程序代码中访问正则表达式匹配之后的分组内容,还可以在正则表达式内部访问之前的分组,称为"反向引用分组"。正则表达式中反向引用语法是"\组编号",组编号是从1开始的。如果组有名字,也可以用组名反向引用,语法是"(?P=组名)"。

示例代码如下

import re

p = r'<([\w]+)>.*</([\w]+)>' # 用于匹配 HTML 或 XML 等标记语言中的标签结构,并捕获标签的名称。


m = re.search(p,'<a>abc</a>')
print(m)

m = re.search(p,'<a>abc</b>')
print(m)

# 使用组编号反向引用
print()
p = r'<([\w]+)>.*</\1>'
'''
使用组名来反向引用
p = r"<(?P<tag>[\w]+)>.*</(?P=tag)>"
'''

m = re.search(p,'<a>abc</a>')
print(m)

m = re.search(p,'<a>abc</b>')
print(m)

代码运行结果

<re.Match object; span=(0, 10), match='<a>abc</a>'>
None

<re.Match object; span=(0, 10), match='<a>abc</a>'>
None

Process finished with exit code 0

非捕获分组

前文介绍的分组称为"捕获分组"。捕获分组的匹配结果被暂时保存到内存中,以备正则表达式或其他程序引用。这个过程称为"捕获",捕获结果可以通过组编号或组名进行引用。能够反向引用分组就是因为分组时捕获的。如果正则表达式比较复杂,要处理的文本有很多,可能严重影响性能。因此当使用分组,又不需要引用分组时,可使用"非捕获分组",在组开头使用"?:" 可以实现非捕获分组。需要注意非捕获分组不会匹配组内容。

示例代码如下

import re

p = r'(?:121){2}'
m = re.search(p,'121121abcabc')
print(m)
print(m.group())

print()
p = r'(?:\d{3,4}-(?:\d{7,8}))'
m = re.search(p,'010-12345678')
print(m)
print(m.group())
print(m.groups())

代码运行结果

<re.Match object; span=(0, 6), match='121121'>
121121

<re.Match object; span=(0, 12), match='010-12345678'>
010-12345678
()

Process finished with exit code 0

re模块中重要函数

re是Python内置的正则表达式模块

search() 和 match() 函数

函数名 说明
search(a,b) 在输入的字符串中查找,返回第一个匹配的内容,如果找到则match 对象;如果没有找到则返回None;其中参数 a 为查找的规则(正则表达式),参数 b 为要查找的字符串
match(a,b) 在输入的字符串开始处查找匹配内容,如果找到一个则match 对象;如果没有找到则返回None;其中参数 a 为查找的规则(正则表达式),参数 b 为要查找的字符串

函数名 说明
group() 返回匹配的字符串
start() 返回子字符串的开始索引
end() 返回子字符串的结束索引
span() 返回子字符串的跨度
[match对象的常用方法]

示例代码如下

import re

p = r'\w+@zhangsan\.com'

text = "Tony's email is tony_guan588@zhangsan.com"
m = re.search(p,text)
print(m) # successful

m1 = re.match(p,text)
print(m1) #fail

text1 = "tony_guan588@zhangsan.com"
m1 = re.match(p,text1)
print(m1) # successful
# match对象的几个方法:
print()
print(m1.group()) # 返回匹配的字符串
print(m1.start()) # 返回子字符串的开始索引
print(m1.end()) # 返回子字符串的结束索引
print(m1.span()) # 返回子字符串的跨度

代码运行结果

<re.Match object; span=(16, 41), match='tony_guan588@zhangsan.com'>
None
<re.Match object; span=(0, 25), match='tony_guan588@zhangsan.com'>

tony_guan588@zhangsan.com
0
25
(0, 25)

Process finished with exit code 0

findall() 和 finditer() 函数

示例代码如下

import re

p = r'[Pp]ython'
text = 'I like Python and python'

match_list = re.findall(p,text) # 返回match 列表对象
print(match_list)

match_iter = re.finditer(p,text) # 返回可迭代对象
for m in match_iter:
    print(m.group())

代码运行结果

['Python', 'python']
Python
python

Process finished with exit code 0

字符串分割

字符串分割使用 split() 函数,该函数按照匹配的子字符串进行字符串分割,返回字符串列表对象,split()函数语法如下

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

其中参数 pattern 是正则表达式;参数 string 是要分割的子字符串;参数 maxsplit 是最大分割次数,默认值为0,表示分割次数没有限制;参数 flags 是编译标志。

示例代码如下

import re

p = r'\d+'

# 根据匹配的规则即匹配到数字就分割
text = 'AB12CD34EF'
clist = re.split(p,text)
print(clist)

text1 = 'AB12CD34EF56'
clist1 = re.split(p,text1)
print(clist1)

clist2 = re.split(p,text,maxsplit=1) # 设置最大分割次数为1
print(clist2)

clist3 = re.split(p,text,maxsplit=2) # 设置最大可能分割次数为2
print(clist3)

代码运行结果

['AB', 'CD', 'EF']
['AB', 'CD', 'EF', '']
['AB', 'CD34EF']
['AB', 'CD', 'EF']

Process finished with exit code 0

字符串替换

字符串替换使用 sub() 函数,该函数用于替换匹配的子字符串,返回值是替换之后的字符串,其语法如下

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

其中参数 pattern 是正则表达式;参数 rep 是替换字符串;参数 string 是要提供的字符串;参数 count 是要替换的最大数量,默认值为0,表示替换数量没有限制;参数 flags 是编译标志。

示例代码如下

import re

p = r'\d+'
text = 'AB12CD34EF56'

replace_text = re.sub(p,' ',text)
print(replace_text)

replace_text1 = re.sub(p,' ',text,count=1) # 只替换一次
print(replace_text1)

代码运行结果

AB CD EF 
AB CD34EF56

Process finished with exit code 0

编译正则表达式

编译的正则表达式可以重复使用,这样能减少正则表达式的解析和验证,提高效率。在 re 模块中的 compile() 函数可以编译正则表达式,其语法如下:

re.compile(pattern [, flags=0])

其中参数 pattern 是正则表达式,参数 flags 是编译标志,compile() 函数返回一个编译的正则表达式对象 regex。

已编译正则表达式对象

compile() 函数返回一个编译的正则表达式对象,该对象也提供了与 re 模块类似的方法。下面表格中参数 pos 为开始查找索引,参数 endpos 为结束查找索引。

常用函数 已编译正则表达式对象方法 re 模块函数
search() regex.search(string[,pos[,endpos]]) re.search(pattern,string,flags=0)
match() regex.match(string[pos,[endpos]]) re.match(pattern,string,flags=0)
findall regex.findall(string[pos,[endpos]]) re.findall(pattern,string,flags=0)
finditer() regex.finditer(string[pos,[endpos]]) re.finditer(pattern,string,flags=0)
sub() regex.sub(repl,string,count=0) re.sub(pattern,repl,string,count=0,flags=0)
split() regex.split(string,maxsplit=0) re.split(pattern,string,maxsplit=0,flags=0)
[已编译正则表达式对象方法与 re 模块函数对照]

示例代码如下

import re

# 通过编译的正则表达式对象 regex 调用方法实现文本匹配、查找和替换操作 

p = r'\w+@zhangsan\.com'
regex = re.compile(p) # 编译正则表达式

text = "Tom's email is tom_guan588@zhangsan.com."
m = regex.search(text)
print(m)

p1 = r'[Pp]ython'
regex1 = re.compile(p1) # 编译正则表达式
text1 = 'I like Python and python'
match_list = regex1.findall(text1)
print(match_list)

match_iter = regex1.finditer(text1)
for m in match_iter:
    print(m)

p = r'\d+'
regex = re.compile(p) # 编译正则表达式
text3 = 'AB12CD34EF56'
clist = regex.split(text3)
print(clist)
replace_text = regex.sub(' ',text3)
print(replace_text)

代码运行结果

<re.Match object; span=(15, 39), match='tom_guan588@zhangsan.com'>
['Python', 'python']
<re.Match object; span=(7, 13), match='Python'>
<re.Match object; span=(18, 24), match='python'>
['AB', 'CD', 'EF', '']
AB CD EF 

Process finished with exit code 0

编译标志

compile() 函数编译正则表达式对象时,还可以设置编译标志。编译标志可以改变正则表达式引擎行为。

(1)ASCII 和 Unicode

通过编译标志 re.ASCII(或re.A)设置采用ASCII编码,通过编译标志 re.UNICODE(或re.U)设置采用 Unicode 编码。

示例代码如下

import re

text = '你们好 Hello'

p = r'\w+'
regex = re.compile(p,re.U) # 设置采用Unicode编码,此时所有字符都可以匹配

m = regex.search(text)
print(m)

m = regex.match(text)
print(m)

regex = re.compile(p,re.A) # 设置采用ASCII编码,此时中文并不会显示出来

m = regex.search(text)
print(m)

m = regex.match(text) # 匹配失败,因为开头为中文,而ASCII编码并没有中文
print(m)

代码运行结果

<re.Match object; span=(0, 3), match='你们好'>
<re.Match object; span=(0, 3), match='你们好'>
<re.Match object; span=(4, 9), match='Hello'>
None

Process finished with exit code 0
(2)忽略大小写

默认情况下正则表达式引擎对大小写是敏感的,可以通过 re.IGNORECASE(或re.I)实现忽略大小写。

示例代码如下

import re

p = r'(java).*(python)'
regex = re.compile(p,re.I)

m = regex.search('I like Java and Python.')
print(m)

m1 = regex.search('I like JAVA and Python.')
print(m1)

代码运行结果

<re.Match object; span=(7, 22), match='Java and Python'>

Process finished with exit code 0
(3)点元字符匹配换行符

默认情况下正则表达式引擎中点 "." 元字符可以匹配除换行符外的任何字符,但是有时候需要点 "." 元字符也能匹配换行符,此时可以通过 re.DOTALL(或re.S)实现。

示例代码如下

import re

p = r'.+'
regex = re.compile(p) # 没有设置编译标志

m = regex.search('Hello\nWorld.')
print(m)

regex1 =re.compile(p,re.DOTALL)

m1 = regex1.search('Hello\nWorld.')
print(m1)

代码运行结果

<re.Match object; span=(0, 5), match='Hello'>
<re.Match object; span=(0, 12), match='Hello\nWorld.'>

Process finished with exit code 0
(4)多行模式

编译标志 re.MULTILINE(或 re.M)可以设置多行模式,默认情况下 ^ 和 $ 匹配字符串的开始和结束,而在多行模式下 ^ 和 $ 匹配任意一行的开始和结束。

示例代码如下

import re

p = r'^World'
regex = re.compile(p) # 未设置多行模式

m = regex.search('Hello\nWorld.')
print(m)

regex1 = re.compile(p,re.M) # 设置多行模式

m1 = regex1.search('Hello\nWorld.')
print(m1)

代码运行结果

None
<re.Match object; span=(6, 11), match='World'>

Process finished with exit code 0
(5)详细模式

编译标志 re.VERBOSE(或 re.X)可以设置详细模式,详细模式下可以在正则表达式中添加注释,可以有空格和换行。

示例代码如下

import re

p = '''(java) # 匹配java字符串
        .*    # 匹配任意字符零或多个
        (python) # 匹配Python字符串
        '''
# 由于正则表达式中包含了换行等符号,所以需要使用双重单引号或三重单引号括起来,而不是原始字符串

regex = re.compile(p,re.I | re.VERBOSE) # 设置了两个编译标志

m = regex.search('I like Java and Python.')
print(m)

m1 = regex.search('I like JAVA and Python.')
print(m1)

m2 = regex.search('I like java and Python.')
print(m2)

代码运行结果

<re.Match object; span=(7, 22), match='Java and Python'>
<re.Match object; span=(7, 22), match='JAVA and Python'>
<re.Match object; span=(7, 22), match='java and Python'>

Process finished with exit code 0

参考书籍:《python从小白到大牛》(第2版)关东升 编著


文章创作不易,本文共11000+字,为了大家能理解,写的很详细,这也让我花了很多时间。最后,如果觉得本文对大家有帮助的话,还请给我点个赞和关注,谢谢大家!!!

相关推荐
张紫娃1 分钟前
【鸿蒙学习笔记】创建自定义组件
笔记·学习·harmonyos
如影随从17 分钟前
04-ArcGIS For JavaScript的可视域分析功能
开发语言·javascript·arcgis·可视域分析
XiaoCCCcCCccCcccC21 分钟前
C语言实现双向链表
c语言·开发语言·链表
十年一梦实验室24 分钟前
【C++】相机标定源码笔记- RGB 相机与 ToF 深度传感器校准类
开发语言·c++·笔记·数码相机·计算机视觉
Tech Synapse28 分钟前
Java循环创建对象内存溢出怎么解决
java·开发语言·jvm
IT·陈寒28 分钟前
Kotlin vs Java:深入解析两者之间的最新差异与优劣(全面指南)
java·python·kotlin
蜉蝣之翼❉33 分钟前
c++ 简单线程池
开发语言·c++
WHYBIGDATA44 分钟前
Scala中高级的函数编程
开发语言·后端·scala
知识分享小能手1 小时前
从新手到高手:Scala函数式编程完全指南,Scala 访问修饰符(6)
大数据·开发语言·后端·python·数据分析·scala·函数式编程
elderingezez1 小时前
2024年用scrapy爬取BOSS直聘的操作
爬虫·python·scrapy