Python / 26 Python 中使用正则表达式

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 匹配 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.imooc.com')
>>> matchObject.group()
'www'
>>> matchObject.span()
(0, 3)
  • 在第 1 行,导入模块 re
  • 在第 2 行,在字符串 ‘www.imooc.com’ 中查找模式 ‘w+’
    • 该模式匹配连续的小写字符 W
    • 如果找到模式匹配的子字符串,则返回一个匹配对象 matchObject
  • 在第 3 行,匹配对象 matchObject.group() 方法返回匹配的字符串
  • 在第 5 行,匹配对象 matchObject.span() 方法返回一个元组
    • 元组的第 0 项,匹配的字符串在原始字符串中的起始位置
    • 元组的第 1 项,匹配的字符串在原始字符串中的结束位置
>>> import re
>>> matchObject = re.match('W+', 'www.imooc.com')
>>> matchObject is None
True
  • 在第 1 行,导入模块 re
  • 在第 2 行,在字符串 ‘www.imooc.com’ 中查找模式 ‘W+’
    • 该模式匹配连续的大写字符 W
    • 如果找不到模式匹配的子字符串,则返回一个 None
>>> import re
>>> matchObject = re.match('o+', 'www.imooc.com')
>>> matchObject is None
True
  • 在第 1 行,导入模块 re
  • 在第 2 行,在字符串 ‘www.imooc.com’ 中查找模式 ‘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.imooc.com')
>>> matchObject.group()
'oo'
>>> matchObject.span()
(6, 8)
  • 在第 1 行,导入模块 re
  • 在第 2 行,在字符串 ‘www.imooc.com’ 中查找模式 ‘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.imooc.com/wiki')
parseUrl('http://www.imooc.com/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.imooc.com/wiki
group(1) = https
group(2) = www.imooc.com
group(3) = wiki

group(0) = http://www.imooc.com/courses
group(1) = http
group(2) = www.imooc.com
group(3) = courses