Python 中使用正则表达式
1. 正则表达式
1.1 简介
正则表达式 (regular expression) 描述了一种字符串匹配的模式 (pattern),例如:
- 模式 ab+c
- 可以匹配 abc、abbc、abbbc
- 代表前面的字符出现 1 次或者多次
- 模式 ab*c
- 可以匹配 ac、abc、abbc
- ? 代表前面的字符出现 0 次或者多次
- 模式 ab?c
- 可以匹配 ac、abc
- ? 代表前面的字符出现 0 次或者 1 次
它的用途包括:
- 检查一个串是否含有某种子串
- 将匹配的子串替换
- 从某个串中取出符合某个条件的子串
1.2 普通字符
正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为"元字符")组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。
普通字符包括没有显式指定为元字符的所有可打印和不可打印字符。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。
1.3 特殊字符
特殊字符是一些有特殊含义的字符,例如的 ab*c 中的 *,* 之前的字符是 b,* 表示匹配 0 个或者多个 字符 b。下表列出了正则表达式中的特殊字符:
特殊字符 | 描述 |
---|---|
\t | 制表符 |
\f | 换页符 |
\n | 换行符 |
\r | 回车符 |
\s | 匹配任意空白字符,等价于 [\t\n\r\f] |
\S | 匹配任意非空字符 |
\d | 匹配任意数字,等价于 [0-9] |
\D | 匹配任意非数字 |
^ | 匹配字符串的开头 |
$ | 匹配字符串的末尾 |
. | 匹配任意字符 |
\b | 匹配一个单词边界,在单词的开头或者末尾匹配xcd ef’ |
\B | 匹配非单词边界 |
[…] | 用来表示一组字符 |
… | 不在[]中的字符 |
re* | 匹配 0 个或多个正则表达式 |
re+ | 匹配 1 个或多个正则表达式 |
re? | 匹配 0 个或 1 个正则表达式 |
re{n} | 匹配 n 个正则表达式 |
re{n,m} | 匹配 n 到 m 个正则表达式 |
a | b |
(re) | 对正则表达式分组并记住匹配的文本 |
2. 模块 re
2.1 简介
Python 提供了 re 模块,提供正则表达式的模式匹配功能。在 re 模块中定义了如下常用函数:
函数 | 功能 |
---|---|
re.match(pattern, string, flags) | 从字符串 string 的起始位置,查找符合模式 pattern 的子串 |
re.search(pattern, string, flags) | 从字符串 string 的任意位置,查找符合模式 pattern 的子串 |
re.split(pattern, string) | 根据分隔符 pattern 将字符串 string 分割 |
re.sub(pattern, replace, string) | 将字符串中匹配模式 patter 的子串替换字符串 replace |
2.2 正则表达式修饰符
正则表达式可以包含一些可选修饰符来控制匹配的模式。修饰符被指定为一个可选的标志,多个标志可以通过按位 OR(|) 它们来指定,如 re.I | re.M 被设置成 I 和 M 标志。下表列举了常用的正则表达式修饰符:
修饰符 | 描述 |
---|---|
re.I | 使匹配对大小写不敏感 |
re.M | 多行匹配,影响 ^ 和 $ |
2.3 re.MatchObject
re.MatchObject 表示模式匹配的结果,该对象包含 3 个成员方法:
- start() 返回匹配开始的位置
- end() 返回匹配结束的位置
- span() 返回一个元组包含匹配 (开始,结束) 的位置
2.4 re.RegexObject
re.RegexObject 表示正则表示对象,该对象包含 2 个成员方法:
- match(string) | 从字符串 string 的起始位置,查找符合模式 pattern 的子串
- serach(string) | 从字符串 string 的任意位置,查找符合模式 pattern 的子串
3. 在字符串查找与模式匹配的字符串
3.1 从字符串的起始位置进行匹配
函数 re.match(pattern, string, flags = 0) 用于在字符串查找与模式匹配的字符串:
- 从字符串 string 的起始位置,查找符合模式 pattern 的子串
- 如果匹配成功,则返回一个 re.MatchObject 对象
- 如果匹配失败,则返回 None
- 参数 flags,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等
函数的使用示例如下:
>>> import re
>>> matchObject = re.match('w+', 'www.linkdao.cn')
>>> matchObject.group()
'www'
>>> matchObject.span()
(0, 3)
- 在第 1 行,导入模块 re
- 在第 2 行,在字符串 ‘www.linkdao.cn’ 中查找模式 ‘w+’
- 该模式匹配连续的小写字符 W
- 如果找到模式匹配的子字符串,则返回一个匹配对象 matchObject
- 在第 3 行,匹配对象 matchObject.group() 方法返回匹配的字符串
- 在第 5 行,匹配对象 matchObject.span() 方法返回一个元组
- 元组的第 0 项,匹配的字符串在原始字符串中的起始位置
- 元组的第 1 项,匹配的字符串在原始字符串中的结束位置
>>> import re
>>> matchObject = re.match('W+', 'www.linkdao.cn')
>>> matchObject is None
True
- 在第 1 行,导入模块 re
- 在第 2 行,在字符串 ‘www.linkdao.cn’ 中查找模式 ‘W+’
- 该模式匹配连续的大写字符 W
- 如果找不到模式匹配的子字符串,则返回一个 None
>>> import re
>>> matchObject = re.match('o+', 'www.linkdao.cn')
>>> matchObject is None
True
- 在第 1 行,导入模块 re
- 在第 2 行,在字符串 ‘www.linkdao.cn’ 中查找模式 ‘o+’
- 该模式匹配连续的小写字符 o
- 如果找不到模式匹配的子字符串,则返回一个 None
- 在第 4 行,显示匹配结果是 None
- 尽管字符 string 的中间含有字符串 oo
- 函数 re.match 从字符串 string 的开始位置进行匹配
- 因此找不到匹配
3.2 从字符串的任意位置进行匹配
函数 re.search(pattern, string, flags = 0) 用于在字符串查找与模式匹配的字符串:
- 从字符串 string 的任意位置,查找符合模式 pattern 的子串
- 如果匹配成功,则返回一个 re.MatchObject 对象
- 如果匹配失败,则返回 None
- 参数 flags,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等
>>> import re
>>> matchObject = re.search('o+', 'www.linkdao.cn')
>>> matchObject.group()
'oo'
>>> matchObject.span()
(6, 8)
- 在第 1 行,导入模块 re
- 在第 2 行,在字符串 ‘www.linkdao.cn’ 中查找模式 ‘o+’
- 该模式匹配连续的小写字符 o
- 如果找到模式匹配的子字符串,则返回一个匹配对象 matchObject
- 在第 3 行,匹配对象 matchObject.group() 方法返回匹配的字符串
- 在第 5 行,匹配对象 matchObject.span() 方法返回一个元组
- 元组的第 0 项,匹配的字符串在原始字符串中的起始位置
- 元组的第 1 项,匹配的字符串在原始字符串中的结束位置
3.3 在字符串的首部进行匹配
>>> import re
>>> re.search('^a', 'abc')
<_sre.SRE_Match object; span=(0, 1), match='a'>
>>> re.search('^a', 'xabc')
>>>
- 在第 2 行,^a 表示从字符串 ‘abc’ 的首部进行匹配
- 在第 3 行,显示匹配结果不为 None
- 在第 4 行,^a 表示从字符串 ‘xabc’ 的首部进行匹配
- 在第 5 行,显示匹配结果为 None
3.4 在字符串的尾部进行匹配
>>> import re
>>> re.search('c$', 'abc')
<_sre.SRE_Match object; span=(2, 3), match='c'>
>>> re.search('c$', 'abcx')
>>>
- 在第 2 行,c$ 表示从字符串 ‘abc’ 的尾部进行匹配
- 在第 3 行,显示匹配结果不为 None
- 在第 4 行,c$ 表示从字符串 ‘xabc’ 的尾部进行匹配
- 在第 5 行,显示匹配结果为 None
3.5 匹配一串数字
>>> import re
>>> re.search('\d+', 'abc123xyz')
<_sre.SRE_Match object; span=(3, 6), match='123'>
>>> re.search('\d{3}', 'abc123xyz')
<_sre.SRE_Match object; span=(3, 6), match='123'>
>>> re.search('\d{4}', 'abc123xyz')
>>>
- 在第 2 行,\d+ 表示匹配 1 个或者多个数字
- 在第 3 行,显示匹配结果不为 None
- 在第 4 行,\d{3} 表示匹配 3 个数字
- 在第 5 行,显示匹配结果不为 None
- 在第 6 行,\d+ 表示匹配 4 个数字
- 在第 7 行,显示匹配结果为 None
3.6 判断是否是合法的变量名
Python 的变量命名规则如下:
- 首个字符必须是字母或者字符 _
- 其余的字符可以是字符、数字或者字符 _
下面的例子使用正则表达式判断字符串是否是一个合法的变量名称:
import re
def isPythonId(id):
pattern = '^[a-zA-Z\_][a-zA-Z0-9\_]\*$'
matchObject = re.search(pattern, id)
if matchObject is None:
print('%s is not Id' % id)
else:
print('%s is Id' % id)
isPythonId('abc')
isPythonId('Abc\_123')
isPythonId('123')
- 在第 3 行,定义了函数 isPythonId(id),判断输入字符串 id 是否是一个合法的 Python 变量名
- 在第 4 行,模式 pattern 定义了一个合法的 Python 变量名的模式,该模式由 4 个部分构成
模式 | 功能 |
---|---|
^ | 匹配字符串头部,即被匹配的字符串从原始字符串的头部开始 |
[a-zA-Z_] | 匹配小写字符、大写字符和字符 _ |
[a-zA-Z0-9_] | 匹配小写字符、大写字符、数字和字符 _ |
* | 将 * 之前的字符重复 0 次或者多次 |
$ | 匹配字符串尾部,即被匹配的字符串以原始字符串的尾部结尾 |
程序运行输出结果如下:
abc is Id
Abc_123 is Id
123 is not Id
4. 将字符串分割成多个部分
函数 re.split(pattern, string) 根据分隔符 pattern 将字符串 string 分割
- 返回一个列表,该列表记录了分割的字符串
- 参数 pattern,描述了分隔符的模式
- 参数 string,是被分割的字符串
>>> import re
>>> re.split('[ :]', 'www imooc:com')
['www', 'imooc', 'com']
>>> re.split(' +', 'www imooc com')
['www', 'imooc', 'com']
5. 在字符串替换与模式匹配的字符串
5.1 替换字符串
函数 re.sub(pattern, replace, string, count=0, flags=0) 用于替换字符串:
- 在字符串 string 中查找与模式 pattern 匹配的子串,将其替换为字符串 replace
- 参数 replace,是被替换的字符串,也可为一个函数
- 参数 count,模式匹配后替换的最大次数,默认 0 表示替换所有的匹配
- 参数 flags,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等
import re
line = 'number = 123 # this is comment'
result = re.sub('\d+', 'NUMBER', line)
print(result)
result = re.sub('#.\*$', '', line)
print(result)
- 在第 4 行,搜索字符串 line,将与模式 ‘\d+’ 匹配的字符串替换为 ‘NUMBER’
- 模式 ‘\d+’ 匹配多个连续的数字
- 在第 6 行,搜索字符串 line,将与模式 ‘#.*$’ 匹配的字符串替换为 ‘’
- 替换为空字符串,即删除匹配的字符串
- 模式 ‘#.*$’ 匹配从字符 # 开始到到结尾的字符串,即行的注释
程序输出结果:
number = NUMBER # this is comment
number = 123
- 在第 1 行,将数字 123 替换为 NUMBER
- 在第 1 行,将以 # 开始的注释删除
5.2 使用函数替换字符串
参数 replace 用于替换匹配的字符串,它可以是一个函数。下面的例子将匹配的数字乘以 2:
import re
def replace(matchedObject):
text = matchedObject.group()
number = int(text)
return str(number \* 2)
line = 'number = 123'
result = re.sub('\d+', replace, line)
print(result)
- 在第 8 行,定义了原始字符串 line
- 在第 9 行,使用 re.sub 搜索符合模式 ‘\d+’ 的字符串,使用函数 replace 进行替换
- re.sub 找到符合模式 ‘\d+’ 的字符串时,将匹配结果传递给 replace
- 函数 replace 根据匹配结果,返回一个字符串
- re.sub 将符合模式的字符串替换为函数 replace 的返回结果
- 在第 3 行,定义了函数 replace
- 在第 4 行,matchedObject.group() 返回匹配模式的字符串
- 在第 5 行,将匹配的字符串转换为整数
- 在第 6 行,将整数乘以 2 后转换为字符串,并返回
程序输出结果如下:
number = 246
6. 分组与捕获
6.1 简介
正则表达式中的分组又称为子表达式,就是把一个正则表达式的全部或部分当做一个整体进行处理,分成一个或多个组。其中分组是使用 () 表示的。进行分组之后 ()里面的内容就会被当成一个整体来处理。
把正则表达式中子表达式匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用,被称为捕获。
6.2 分析 URL
import re
def parseUrl(url):
pattern = '(.\*)://(.\*)/(.\*)'
matchObject = re.search(pattern, url)
all = matchObject.group(0)
protocol = matchObject.group(1)
host = matchObject.group(2)
path = matchObject.group(3)
print('group(0) =', all)
print('group(1) =', protocol)
print('group(2) =', host)
print('group(3) =', path)
print()
parseUrl('https://www.linkdao.cn/wiki')
parseUrl('http://www.linkdao.cn/courses')
- 在第 3 行,函数 parseUrl(url) 分析 URL 的组成部分
- URL 由 3 部分构成:协议、主机名、路径名
- 在第 4 行,定义了匹配 URL 的模式 ‘(.)://(.)/(.*)’
- 第 1 个 (.*) 匹配协议
- 第 2 个 (.*) 匹配主机名
- 第 3 个 (.*) 匹配路径名
- 匹配对象 matchObject 的 group(index) 方法返回指定分组号的分组
- group(0) 为匹配整个表达式的字符串
- group(1) 为匹配的协议
- group(2) 为匹配的主机名
- group(3) 为匹配的路径名
程序运行输出:
group(0) = https://www.linkdao.cn/wiki
group(1) = https
group(2) = www.linkdao.cn
group(3) = wiki
group(0) = http://www.linkdao.cn/courses
group(1) = http
group(2) = www.linkdao.cn
group(3) = courses