正则表达式

参考stackoverflow关于正则

正则表达式概念

字符和位置

正则表达式中有两个概念,一个字符串包含若干个字符,每个字符在内存中都有对应的二进制编码,以及字符先后关系构成的位置,比如字符串开始位置和结束位置如图所示表示为 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,并自动编号
引用编号为的分组匹配到的字符串,例如 (d)abc1
(?Pexp) 为分组命名,例如 (?Pab){2},匹配 abab
(?P=name) 引用命名为的分组匹配到的字符串,例如 (?Pd)abc(?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: 表达式中有别名的分组的别名为键、以组编号为值的字典,不含无别名的分组。

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