正则表达式
正则表达式概念

正则表达式中有两个概念,一个字符串包含若干个字符,每个字符在内存中都有对应的二进制编码,以及字符先后关系构成的位置,比如字符串开始位置和结束位置如图所示表示为 ps 和 pe。包含 N 个字符的字符串有 N+1 个位置,位置不占用内存,仅用于匹配定位。 匹配过程中有一个位置指针,开始总是指向位置 ps,根据匹配模式每匹配一次,就将指针移动到匹配字符的后序位置,并尝试在每一个位置上进行模式匹配,直至尝试过 pe 位置后匹配过程结束。
断言
在正则表达式中,断言(assertion)是一种特殊的语法,用于在匹配时对模式的周围进行条件约束,而不是匹配实际的字符。断言用于指定一个条件,该条件必须满足或不满足,以便继续或停止匹配。
零宽断言(zero-width assertions)是正则表达式中的一种特殊语法,用于指定匹配位置而不消耗实际的字符。它们被称为”零宽”,因为它们本身不匹配任何字符,仅匹配位置。
特殊字符
. [ { ( ) \ * + ? | ^ $
匹配字符的元字符
| 元字符 | 字符集 | 非集 | 字符集 |
|---|---|---|---|
| . | 匹配除换行符 \n 外的所有字符 | \n | 换行符 \n |
| \d | 匹配数字 0-9 | \D | 非数字 |
| \s | 空白符: | [<空格>trnfv] | \S 非空白符 |
| \w | 匹配单词字符 | \W | 非单词字符 |
[…] 用于直接指定字符集,表示匹配其中任意一个:
可以直接给出,比如 [abc]
可以给定范围,比如 [a-c]
以在开始位置添加 ^,表示取反,比如 [^a-c],表示 abc 以外的所有字符集。
如果要在 [] 中指定特殊字符,比如 ^,需要转义。
匹配位置的元字符
| 元字符 | 字符集 | 非集 | 字符集 |
|---|---|---|---|
| ^ | 匹配字符串起始位置 ps | $ | 匹配字符串末尾位置 pe |
| \b | 匹配 | \w 和 \W 之间位置,ps,p2,p3,pe | \B |
| \A | 等同 ^ | \Z | 等同 $ |
^ 和 $ 在多行模式下支持每行的起始和末尾位置匹配。\A 和 \Z 不支持多行模式。
^ 在数学中被称为 hat ,帽子总是戴在头上,匹配字符起始位置,而 $ 很像蛇的尾巴,匹配字符结尾。
A 和 Z 分别是字母表的首尾字母,分别匹配字符起止位置。
b 表示 between,是 \w 和 \W 单词字符和非单词字符之间的位置。
关于\b
虽然字符串的结束位置在实际的字符串内容中没有显式的标记,但在正则表达式的解析过程中,字符串的结束位置被隐式地视为一个特殊的位置,可以用于匹配零宽度的断言,如单词边界 \b 和结束符 $。
量词
有了元字符,只能够匹配特定的单个字符或者位置,有了重复字符的参与,就可以生成更加复杂的模式,比如我们要匹配 8 个数字,不用写 8个 \d,而直接用 \d{8}。 重复字符又称为数量符,常用的重复字符表如下:
| 数量符 | 描述 |
|---|---|
| * | 重复 >=0 次 |
| + | 重复 >=1 次 |
| ? | 重复 0 或 1 次 |
| {m} | 重复 m 次 |
| {m,n} | 重复 m 到 n 次 |
| (,n) | 重复 0 到 n 次 |
| (m,) | 重复 m 到无限次 |
重复字符用在匹配字符的元字符之后,也可以用在分组后
以上重复模式默认为贪婪模式,总是选择尽量多匹配的分支,比如 {m, n} 就尽量选择靠近 n 的分支,可以在其后加 ‘?’ 变成非贪婪模式,比如 *?,{m,n}?
分支和分组
分支|
它用在多个表达式式中间,表示匹配其中任何一个,比如 A | B | C,它总是先尝试匹配左边的表达式,一旦成功匹配则跳过右边的表达式。
如果 | 没有包含在 () 中,则它的范围是整个表达式。 使用 () 括起来的表达式,被称为分组(Group)。重复字符可以加在分组之后。 表达式中的每个分组从左至右被自动从 1 编号,可以在表达式中引用编号。也可以为分组指定名字。
| 分组操作 | 描述 |
|---|---|
| (exp) | 匹配exp,并自动编号 |
| 引用编号为 |
|
| (?P |
为分组命名,例如 (?P |
| (?P=name) | 引用命名为 |
| (?:exp) | 匹配exp,但跳过匹配字符,且不为该分组编号 |
| (?#comment) | 正则表达式注释,不影响正则表达式的处理 |
分组操作还支持以下语法,用于匹配特定位置:
非捕获组
非捕获组(Non-Capturing Groups)在正则表达式中是一个非常有用的工具。它的主要意义在于增强正则表达式的灵活性和性能,同时避免不必要的捕获。 假设你有一个字符串 a1b2c3,并且你想匹配字母和数字的组合,但只捕获数字
(?:[a-z])(\d)
非捕获组的语法是 (?:…),其中 … 是你想要分组的正则表达式部分。
正向先行断言
正则表达式中的正向先行断言(positive lookahead assertion)是一种特殊的断言模式,用于匹配在某个位置之后满足特定条件的文本。正向先行断言在正则表达式中的存在意义是提供更精确、灵活和高效的模式匹配能力,以满足特定的匹配需求,并增强正则表达式的功能和应用范围。零宽度匹配:正向先行断言是零宽度匹配(zero-width match),即它们不消耗匹配项,也不包含在最终的匹配结果中。 例如,Date: 4 Aug 3PM,我们要匹配文本中的小时值。为了只匹配后面有 PM 的数值,我们需要在表达式后面使用正向先行断言 (?=),并在括号内的 = 后面添加 PM。
\d+(?=PM)
负向先行断言
正则表达式中的负向先行断言(negative lookahead assertion)是一种特殊的断言模式,用于匹配在某个位置之后不满足特定条件的文本。负向先行断言是零宽度匹配(zero-width match),即它们不消耗匹配项,也不包含在最终的匹配结果中。这意味着我们可以在不影响最终结果的情况下,仅仅检查文本的一部分是否不满足某些条件 我们要在文本中匹配除小时值以外的数字。我们需要在表达式后面使用负向先行断言 (?!),并在括号内的 ! 后面添加 PM,从而只匹配没有 PM 的数值.
\d+(?!PM)
正向后行断言
正向后行断言(positive lookbehind assertion)是正则表达式中的一种特殊断言模式,用于匹配在某个位置之前满足特定条件的文本。它的语法形式为(?<=…),其中…表示要匹配的条件。正向后行断言是零宽度匹配(zero-width match),即它们不消耗匹配项,也不包含在最终的匹配结果中。这意味着我们可以在不影响最终结果的情况下,仅仅检查文本的一部分是否满足某些条件。 例如,我们要匹配文本Product Code: 1064 Price: $5中的金额数。为了只匹配前面带有 $ 的数字。我们要在表达式前面使用正向后行断言 (?<=),并在括号内的 = 后面添加 $。
(?<= \$\d+)
负向后行断言
例如,我们要在文本中匹配除价格外的数字。为了只匹配前面没有 $ 的数字,我们要在表达式前用负向后行断言 (?<!),并在括号内的 ! 后面添加 $。
(?<! \$\d+)
s = 'Date: $4 Aug 3PM'
# 正向先行断言
pattern = re.compile(r'\d(?=PM)')
# 负向先行断言
pattern = re.compile(r'\d(?!PM)')
# 正向后行断言
pattern = re.compile(r'(?<=\$)\d')
# 负向后行断言
pattern = re.compile(r'(?<!\$)\d')
ret = pattern.findall(s)
匹配模式选项
re 模块定义了 6 种模式选项:
re.I (re.IGNORECASE): 匹配时忽略大小写
re.M (re.MULTILINE): 多行模式,改变’^’和’$’的行为,可以匹配任意一行的行首和行尾
re.S (re.DOTALL): 点任意匹配模式,此时’.’ 匹配任意字符,包含 \n
re.L (re.LOCALE): 使预定字符类 w W b B s S 取决于当前区域设定
re.U (re.UNICODE): 使预定字符类 w W b B s S d D 取决于 unicode 定义的字符属性
re.X (re.VERBOSE): 详细模式。此模式下正则表达式可以写成多行,忽略空白字符,并可以加入注释
# 正则表达式练习
import re
s = """Hello,i am a Student,\ni have three dogs
Hello,You are a good boy
Thank You!
我是一个中国人é
"""
# 忽略大小写
pattern = re.compile(r'h', re.I)
# 多行匹配
pattern = re.compile(r'^Hello', re.M)
# 使 . 匹配包括换行符在内的所有字符
pattern = re.compile(r'.*i ', re.S)
# 允许在正则表达式中使用空格和注释,以提高可读性。
pattern = re.compile(r"""
am\s #am后面是空格
""", re.X)
# 默认情况下,\w 也会匹配非 ASCII 的 Unicode 单词字符
# 使 \w、\W、\b、\B、\d、\D、\s 和 \S 只匹配 ASCII 字符,而不是 Unicode 字符
pattern = re.compile(r'\w', re.A)
ret = pattern.findall(s)
print(ret)
findall 和 finditer
findall() 方法返回匹配的所有子串,并把它们作为一个列表返回。匹配从左到右有序返回子串。如果无匹配,返回空列表。 字符集元字符返回的均是匹配的字符列表,而位置元字符返回的是位置,所以均是空字符。 finditer() 方法与 findall() 唯一不同在于返回的不是列表,而是一个返回 match 对象的迭代器,无匹配,则返回内容为空迭代器。
compile
compile() 方法将字符串形式的表达式编译成匹配模式对象。 第二个参数 flag 指定匹配模式类型,可以按位或运算符 ‘|’ 生效多种模式类型,比如re.I | re.M。另外,也可以在表达式字符串中指定模式,以下两个表达式是等价的:
re.compile(r'abc', re.I | re.M)
re.compile('(?im)abc')
将表达式编译成匹配模式对象后,可以重复使用该对象,无需每次都传入表达式。 pattern 对象提供了几个可读属性用于查看表达式的相关信息:
pattern: 匹配模式对应的表达式字符串。
flags: 编译时用的匹配模式选项,数字形式。
groups: 表达式中分组的数量。
groupindex: 表达式中有别名的分组的别名为键、以组编号为值的字典,不含无别名的分组。
match 和 search
match() 方法从字符段头部开始判断是否匹配,一旦匹配成功,返回一个 Match 对象,否则返回 None。Match 对象保存了首次匹配的结果。 match() 方法与字符串方法 startswith() 很像,只是它使用正则表达式来判断字符头部是否满足条件。 search() 搜索整个字符串,查找匹配的字符,找到后返回一个 match 对象,否则返回 None。
split
split() 方法按照匹配的子串将 string 分割后返回列表。maxsplit 用于指定最大分割次数,不指定将全部分割。
sub 和 subn
sub() 方法使用 repl 替换 string 中每一个匹配的子串后返回替换后的字符串。repl 接受两种类型的参数:
当 repl 是一个字符串时,可以使用 id 或 g
,g 引用分组,id 编号从 1 开始。 当 repl 是一个函数时,它只接受一个match对象作为参数,并返回一个用于替换的字符串(返回的字符串中不可再引用分组)。count用于指定最多替换次数,不指定时全部替换。
escape
escape() 方法对表达式中所有可能被解释为正则运算符的字符进行转义。如果字符串很长且包含很多特殊技字符,而又不想输入一大堆反斜杠,或者字符串来自于用户,且要用作正则表达式的一部分的时候,需要使用这个函数。
match 对象
match 对象保存一次匹配成功的信息,有很多方法会返回该对象,这里对它包含的属性进行介绍。使用上例中的匹配对象,将属性打印如下:
re:匹配时使用的模式
string:要进行匹配操作的字符串
pos 和 endpos:分别表示开始和结束搜索的位置索引,pos 等于 ps,也即 0 位置;这里的 endpos 为 24,等于 - ps,是字符 val1 后的位置,也即 string 的长度。
lastindex:最后一个匹配的分组编号,我们的模式中有 3 个分组,第 3 个分组用于匹配一个 ‘,’。
lastgroup:最后一个匹配的分组的别名,如果没有别名,则为 None。
group():group() 方法使用编号后者别名获取分组,参考 match.group 。
groups():groups() 方法等价于 group(1,2,…last),返回所有分组匹配的子串,是一个元组。
groupdict():groupdict() 方法返回分组中有别名的分组子串,是一个字典,例如 {‘comma’: ‘,’}。
start() 和 end() :分别返回指定分组匹配的字符串的起止字符在 string 上的位置索引值,支持编号和别名。
span(group):等价于 (start(group), end(group)),返回元组类型。
expand(template):将匹配到的分组代入 template 中然后返回,参考 match.expand