# 正则匹配
# 正则表达式
:regular expression ,简称为 regex 。是一个描述字符串的文本模式,用于对其它字符串进行部分匹配。
- 不同软件采用的正则语法可能不同。常见的几种如下:
- POSIX 风格:常用于类 Unix 系统上的老旧软件,又分为两种:
- 基本正则(Basic Regular Expression ,BRE)
- 速记字符集中,不支持 \d、\D 。
- 限定符中,支持 * ,不支持 +、? 。
- 扩展正则(Extended Regular Expression ,ERE)
- 速记字符集中,不支持 \d、\D 。
- GNU 支持的几种正则语法 (opens new window)
- 基本正则(Basic Regular Expression ,BRE)
- Perl 风格:更流行,语法较多,常用于 Perl、Python 等编程语言。
- POSIX 风格:常用于类 Unix 系统上的老旧软件,又分为两种:
- 正则表达式包含的字符分为两大类:
- 普通字符 :指除元字符以外的其它字符。
- 基本的正则引擎只支持 ASCII 字符,而 Python 的 re 模块支持 Unicode 字符。
- 元字符:指在正则语法中具有特殊含义的字符。
- 普通字符 :指除元字符以外的其它字符。
# 普通字符
- 正则表达式中的普通字符包括字母、数字、下划线等,它们会与目标字符串中的字符直接匹配。
- 包括非打印字符,比如:
\r # 回车符 \n # 换行符 \t # 制表符 \f # 换页符
# 元字符
# 转义字符
\
:用于声明一个转义字符。- 如果将元字符声明为转义字符,则可以当作普通字符进行匹配。比如
\.
匹配普通字符.
,\\
匹配普通字符\
。 - 在 Python 的 re 模块的正则表达式中,使用
\\\\
才能匹配普通字符\
。如下:>>> re.findall('\t', '\t\\') ['\t'] >>> re.findall('\\', '\t\\') re.error: bad escape (end of pattern) at position 0 >>> re.findall('\\\\', '\t\\') ['\\'] >>> re.findall(r'\\', '\t\\') # 给字符串加上 r 前缀,将所有 \ 转义成 \\ ['\\']
- 如果将元字符声明为转义字符,则可以当作普通字符进行匹配。比如
# 字符集
[ ]
:用于声明字符集。- 一个字符集是任意个字符的无序集合,用于匹配目标字符串中,单个属于该字符集的字符。
- 如果字符集开头为
^
,则表示反向匹配,即不匹配字符集中的字符。 - 在字符集中,连字符
-
用于声明 ASCII 码连续的一串字符,比如A-Z
、0-9
。- 如果要匹配普通字符
-
,则要用转义字符\-
,或者将-
放在字符集开头或末尾,比如[0-9-]
。
- 如果要匹配普通字符
[
和]
要转义之后才能当作普通字符进行匹配:但是在字符集中,以下元字符不需要转义,就会当作普通字符进行匹配:>>> re.findall(r'\[hi\]', r'[hi]') ['[hi]']
>>> re.findall(r'[.|()^$*+?{}]', '[.|()[]^$*+?{}]') ['.', '|', '(', ')', '^', '$', '*', '+', '?', '{', '}']
- 例:
>>> re.findall(r'[0-9A-Za-z]', 'A1.') ['A', '1'] >>> re.findall(r'[^0-9A-Za-z]', 'A1.') ['.']
正则表达式中,
.
是一个通配符,可以匹配单个任意字符,默认不包括换行符 \n 。正则表达式中,以下转义字符被定义为速记字符集:
\d # 匹配一个数字,相当于 [0-9] \D # 匹配一个非数字,相当于 [^0-9] \w # 匹配一个字母、数字、下划线,相当于 [0-9A-Za-z_] \W # 匹配一个不匹配 \w 的字符 \s # 匹配一个空白字符,相当于 [ \t\n\r\f\v] \S # 匹配一个非空白字符
- 例:
>>> re.findall(r'\d', 'ab12#你好》') ['1', '2'] >>> re.findall(r'\D', 'ab12#你好》') ['a', 'b', '#', '你', '好', '》'] >>> re.findall(r'\w', 'ab12#你好》') ['a', 'b', '1', '2', '你', '好'] >>> re.findall(r'\W', 'ab12#你好》') # 使用 Python 的 re 模块时,\w 匹配所有归类为字母的 Unicode 字符,包括单个汉字,但不包括中文的标点符号 ['#', '》']
- 例:
# 字符组
( )
:用于在正则表达式中声明子表达式,又称为字符组。- 支持嵌套。
- 字符组可以用于从正则匹配的结果中,提取子字符串。如下:
>>> re.findall(r'(A)BC', 'ABC') # 声明了字符组时,只返回该字符组的匹配结果,而不是整个正则表达式的匹配结果 ['A'] >>> re.findall(r'((A)B)C', 'ABC') # 声明了多个字符组时,依次返回其匹配结果 [('AB', 'A')]
(? )
:字符组的扩展语法,常见的几种如下:(?P<name> )
:给该字符组命名。正则匹配时,没必要定义组名。而正则替换时,可以通过字符组的名称或编号,引用其提取的子字符串。编号从 1 开始递增。如下:>>> re.findall(r'(?P<id>(A)B)C', 'ABC') [('AB', 'A')]
>>> re.sub(r'(?P<id>(A)B)C', r'\g<ID>', 'ABC') # 组名区分大小写,如果引用不存在的组名则会报错 IndexError: unknown group name 'ID' >>> re.sub(r'(?P<id>(A)B)C', r'\g<id>', 'ABC') 'AB' >>> re.sub(r'(?P<id>(A)B)C', r'\g<1>', 'ABC') 'AB' >>> re.sub(r'(?P<id>(A)B)C', r'\1', 'ABC') 'AB'
(?# )
:将该字符组视作注释,可以忽略。>>> re.findall(r'(?#test)ABC', 'ABC') ['ABC']
(?: )
:让括号表达式生效,但不捕获字符组。>>> re.sub(r'((A)B)C', r'\1', 'ABC') # 匹配结果中,第一个组是 AB 'AB' >>> re.sub(r'(?:(A)B)C', r'\1', 'ABC') # 匹配结果中,第一个组是 A 'A'
(?= )
:前置断言。如果该字符组匹配当前位置之后的目标字符串,才继续匹配后续的正则表达式。匹配结果中不会包含断言的字符组。>>> re.findall(r'^(?=A)\w+', 'ABC') ['ABC'] >>> re.findall(r'^(?=AA)\w+', 'ABC') []
(?! )
:否定的前置断言。如果该字符组匹配当前位置之后的目标字符串,则不继续匹配后续的正则表达式。>>> re.findall(r'^(?!A)\w+', 'ABC') [] >>> re.findall(r'^(?!AA)\w+', 'ABC') ['ABC']
(?<= )
:后置断言。如果该字符组匹配当前位置之前的目标字符串,才继续匹配后续的正则表达式。>>> re.findall(r'A(?<=A)\w+', 'ABC') ['ABC'] >>> >>> re.findall(r'A(?<=AB)\w+', 'ABC') []
(?<! )
:否定的后置断言。>>> re.findall(r'A(?<!A)\w+', 'ABC') [] >>> re.findall(r'A(?<!AB)\w+', 'ABC') ['ABC']
# 或匹配
|
:用于同时使用两个正则表达式,如果前一个不匹配,则尝试匹配后一个。- 例:
>>> re.findall(r'B|b', 'ABC') ['B'] >>> re.findall(r'abc|ABC', 'ABC') ['ABC'] >>> re.findall(r'(ab|AB)C', 'ABC') ['AB']
- 例:
# 定位符
- 定位符 :用于匹配字符串的边界位置。如下:
- 分为以下几种:
^ # 匹配字符串的开始边界,即第一个字符的之前 $ # 匹配字符串的结束边界,即最后一个字符之后 \b # 匹配单词的开始或结束边界 \B # 匹配除 \b 以外的其它边界
- 定位符并不匹配一个实际存在的字符,而是匹配字符之间的边界位置。
- 如果连续的两个字符,一个匹配 \w ,一个不匹配 \w 或者为 ^ 、$ ,则认为这两个字符之间为单词边界,匹配 \b 。
- 例:
>>> re.findall(r'^', 'Hi!') # 正则表达式只包含定界符时,匹配结果为空字符串 [''] >>> re.findall(r'^Hi', 'Hi!') ['Hi'] >>> re.findall(r'^Hi$', 'Hi!') [] >>> re.findall(r'Hi\b!', 'Hi!') ['Hi!'] >>> re.findall(r'H\Bi!\B', 'Hi!') ['Hi!'] >>> re.findall(r'Hi\b\b\b\b!\B\B$', 'Hi!') # 可以连续使用多个定位符,匹配同一个边界位置 ['Hi!']
- 分为以下几种:
# 限定符
限定符 :用于限制前面一个字符(或字符组)的连续匹配次数。
- 分为以下几种:
* # 连续匹配任意次,包括 0 次 + # 连续匹配至少一次 ? # 连续匹配至多一次,即匹配 0 次或 1 次
- 例:
>>> re.findall(r'.*3','123123123') # 限定符默认采用贪心模式,会尽量匹配最多次 ['123123123'] >>> re.findall(r'.*?3','123123123') # 在限定符之后加上 ? ,比如 *? ,就采用非贪心模式,会尽量匹配最少次 ['123', '123', '123'] >>> re.findall(r'(?:\w{2}3)+','123123123') # 限定符支持修饰字符组,字符组内也支持使用限定符 ['123123123']
- 分为以下几种:
{ }
:用于声明限定符表达式,从而比限定符指定更准确的连续匹配次数。- 格式如下:
{m} # 连续匹配刚好 m 次 {m,} # 连续匹配至少 m 次 {,n} # 连续匹配至多 n 次 {m,n} # 连续匹配至少 m 次且至多 n 次
- m、n 必须都是非负整数,且 m<=n 。
- 逗号前后不能加空格。
- 例:
>>> re.findall(r'e{2}', 'be') [] >>> re.findall(r'e{2}', 'bee') ['ee'] >>> re.findall(r'e{2}', 'beee') ['ee'] >>> re.findall(r'e{2}', 'beeee') ['ee', 'ee']
- 格式如下:
# 示例
- 匹配月份:
>>> re.findall(r'^0?[1-9]$|^1[0-2]$', '09') ['09']
- 匹配一个符合 C 语言命名规范的字符串:
>>> re.findall(r'^[A-Za-z_]\w*$', 'my_var') ['my_var']
- 匹配一个中文姓名:
>>> re.findall(r'^[\u4e00-\u9fa5]{1,5}$', '张三') ['张三']
# import re
:Python 的标准库,提供了一些使用正则表达式的方法。
# compile()
re.compile(pattern, flags=0)
- 作用:创建一个正则表达式对象并返回。
- 参数:
- pattern :输入一个 str 或 bytes 类型的正则表达式。
- flags :标志位,用于控制正则匹配的模式。如下:
re.ASCII # 当 pattern 为 str 类型时,使 \b、\d、\w、\s 及其大写形式,只匹配 ASCII 字符。默认不启用该模式,会匹配 Unicode 字符 re.IGNORECASE # 不区分大小写 re.MULTILINE # 使 ^ 额外匹配换行符的右边界、 $ 额外匹配换行符的左边界,从而对字符串中的每行分别进行正则匹配 re.DOTALL # 使 . 匹配任何字符,即增加匹配换行符 \n re.ASCII|re.IGNORECASE # 可通过或运算符 | 组合启用多个模式
- 使用 re 模块时,可以直接调用 re.findall() 等函数。也可以先用 re.compile() 创建正则表达式对象,再调用其方法,这样便于复用正则表达式。如下:
>>> import re >>> r = re.compile('a+', re.ASCII|re.IGNORECASE) >>> r.findall('abAB') ['a', 'A'] >>> re.findall(r, 'abAB') ['a', 'A']
# findall()
re.findall(pattern, string, flags=0)
- 作用:找出所有正则匹配的子字符串(没有字符重叠),组成一个 list 返回。
- 参数:
- pattern :一个 str、bytes 或 对象类型的正则表达式。
- string :要进行正则匹配的目标字符串。
- 例:
>>> re.findall(r'aa', 'abAB') # 匹配失败时,返回一个空 list [] >>> re.findall(r'.', 'abAB') # 匹配多个子字符串时,依次返回 ['a', 'b', 'A', 'B'] >>> re.findall(r'(ab)A(B)', 'abAB') # 如果声明了字符组,则只返回字符组的匹配结果,不返回整个正则表达式的匹配结果 [('ab', 'B')]
>>> re.findall(r'.*', 'abAB\n') # . 默认不匹配换行符,因此这里会匹配 \n 左侧、右侧的空字符串 ['abAB', '', ''] >>> re.findall(r'.+', 'abAB\n') ['abAB']
# search()
re.search(pattern, string, flags=0)
- 作用:找出第一个正则匹配的子字符串,返回一个 re.Match 对象。匹配失败时,返回 None 。
- 例:
>>> re.search(r'ab', 'abAB') <re.Match object; span=(0, 2), match='ab'>
- 关于 re.Match 对象。
- 用法示例:
>>> match = re.search(r'(ab)A(B)', 'abAB\n') >>> if not match: # 检查匹配是否成功,再进行后续操作 ... exit() ... >>> match <re.Match object; span=(0, 4), match='abAB'> # 这里 match 表示与整个正则表达式匹配的子字符串,而 span 表示该子串的切片范围 >>> match.span() # 返回子串的切片范围 (0, 4)
- 调用 groups() 方法,会将各个字符组的匹配结果组成一个 tuple 返回:
>>> re.search(r'abAB', 'abAB\n').groups() # 如果没有声明字符组,则返回一个空 tuple () >>> re.search(r'(ab)AB', 'abAB\n').groups() ('ab',) >>> re.search(r'(ab)A(B)', 'abAB\n').groups() ('ab', 'B')
- 调用 groupdict() 方法,会将各个已命名的字符组的匹配结果组成一个 dict 返回:
>>> re.search(r'(?P<id>ab)A(B)', 'abAB\n').groupdict() {'id': 'ab'}
- 用法示例:
# sub()
re.sub(pattern, replace, string, count=0, flags=0)
- 作用:找出 string 中正则匹配的子字符串,替换成 replace 字符串。
- 参数:
- count :替换的最大次数。默认为 0 ,表示不限制。
- 例:
>>> re.sub(r'aa', '_', 'abAB\n') # 如果没有正则匹配的子字符串,则返回原字符串 'abAB\n' >>> re.sub(r'(ab)A(B)', '_', 'abAB\n') # sub() 会替换与整个正则表达式匹配的子字符串,而不只是字符组 '_\n' >>> re.sub(r'(?P<id>ab)A(B)', r'\g<id>_\2', 'abAB\n') # 替换时可以引用字符组 'ab_B\n'
# split()
re.split(pattern, string, maxsplit=0, flags=0)
- 作用:找出 string 中正则匹配的子字符串,用它们将 string 分割成多个字符串,组成一个 list 返回。
- 参数:
- maxsplit :分割的最大次数。默认为 0 ,表示不限制。
- 例:
>>> re.split(r'\W+', 'www.baidu.com') ['www', 'baidu', 'com']