Python内置的正则库 re

发布时间 2023-10-21 12:06:33作者: 凌晗

Python- 正则表达式 re 模块

正则表达式(regular expression,regex)

正则表达式模式(pattern)

字符

普通字符和元字符

大多数字母和符号都会简单地匹配自身。例如,正则表达式 test 将会精确地匹配到 test 。(你可以启用不区分大小写模式,让这个正则也匹配 Test 或 TEST ,稍后会详细介绍。)

但该规则有例外。有些字符是特殊的 元字符(metacharacters),并不匹配自身。它们表示匹配一些非常规的内容,或者通过重复它们或改变它们的含义来影响正则的其他部分。

元字符包括:. ^ $ * + ? { } [ ] \ | ( ),它们的作用将在下面具体的类别中介绍。

匹配一种字符

只能匹配字符串中出现的某个确定的字符

  • 普通字符
    • 字母、数字、汉字、下划线、以及没有特殊定义的标点符号,都是 " 普通字符 "。表达式中的普通字符,在匹配一个字符串的时候,匹配与之相同的一个字符。
    • 例如,表达式 “c”,在匹配字符串 “abcde” 时,匹配结果是:成功;匹配到的内容是:“c”;匹配到的位置是:开始于 2,结束于 3。(包含开始位置,不包含结束位置)
  • 转义字符
    • 一些不可打印字符,采用在前面加 \ 的方法来表示它要匹配的内容,例如制表符(\t)、换行符 \n 等;
    • 一些有特殊用处的元字符,在前面加 \ 的方法来代表该符号本身。例如 {,}, [, ], /, \, +, *, ., $, ^, |, ? 等。

匹配多种字符

能够和多种字符都匹配的上,但是只匹配符合条件的第一个,不是全匹配完

  • . (点)
    • 在默认模式,匹配除了换行的任意字符。如果指定了标签 DOTALL ,它将匹配包括换行符的任意字符
    • 例如,表达式 “c.”,在匹配字符串 “abcde” 时,匹配结果是:成功;匹配到的内容是:“cd”;匹配到的位置是:开始于 2,结束于 4。
  • [] 字符集合
    • 字符可以单独列出,比如 [amk] 匹配 'a', 'm', 或者 'k'
    • 可以表示字符范围,通过用 '-' 将两个字符连起来。比如 [a-z] 将匹配任何小写 ASCII 字符, [0-5][0-9] 将匹配从 00 到 59 的两位数字, [0-9A-Fa-f] 将匹配任何十六进制数位。 如果 - 进行了转义 (比如 [a\-z])或者它的位置在首位或者末尾(如 [-a] 或 [a-]),它就只表示普通字符 '-'
    • 特殊字符在集合中,失去它的特殊含义。比如 [(+*)] 只会匹配这几个文法字符 '(''+''*', or ')'
    • 字符类如 \w 或者 \S (如下定义) 在集合内可以接受,它们可以匹配的字符由 ASCII 或者 LOCALE 模式决定。
    • 不在集合范围内的字符可以通过 取反 来进行匹配。如果集合首字符是 '^' ,所有  在集合内的字符将会被匹配,比如 [^5] 将匹配所有字符,除了 '5', [^^] 将匹配所有字符,除了 '^'^ 如果不在集合首位,就没有特殊含义。
    • 在集合内要匹配一个字符 ']',有两种方法,要么就在它之前加上反斜杠,要么就把它放到集合首位。比如, [()[\]{}] 和 []()[{}] 都可以匹配括号。
  • 转义方式表示的特殊字符集
    • \s:匹配 ASCII 中的空白字符,就是 [ \t\n\r\f\v]
    • \S:相当于 \s 取反,匹配 ASCII 中的非空白字符,就是 [^ \t\n\r\f\v]
    • \d:匹配任何十进制数的字符,就是 [0-9]
    • \D:相当于 \d 取反,匹配任何非十进制数的字符,就是 [^0-9]
    • \w:匹配 ASCII 字符中的数字和字母和下划线,就是 [a-zA-Z0-9_] 。如果设置了 LOCALE 标记,就匹配当前语言区域的数字和字母和下划线。
    • \W:相当于 \w 取反,匹配非单词字符的字符,就是 [^a-zA-Z0-9_]。如果使用了 LOCALE 旗标,则会匹配当前区域中既非字母数字也非下划线的字符。

重复

指定正则的某部分必须重复一定的次数。

  • ? 表示匹配一次或零次,即把某项内容变成了可选的
    • 例如,home-?brew 可以匹配 'homebrew' 或 'home-brew'
  • * 表示匹配 零次 或更多次
  • + 表示匹配一次或更多次
    • 例如,ca+t 可以匹配 'cat' ( 1 个 'a' )或 'caaat' ( 3 个 'a'),但不能匹配 'ct'
  • {m,n} 表示必须至少重复 m 次,至多重复 n 次。
    • m 和 n 都是十进制整数,它们不是必填的,缺失的情况下会设定为默认值。缺失 m 会解释为最少重复 0 次 ,缺失 n 则解释为最多重复无限次。
    • 例如,a/{1,3}b 将匹配 'a/b''a//b' 和 'a///b'。 它不能匹配 'ab',因为其中没有斜杠,也不能匹配 'a////b',因为其中有四个斜杠。

位置和断言

零宽度断言:它们不会使解析引擎在字符串中前进一个字符;相反,它们根本不占用任何字符,只是成功或失败。例如,\b 是一个断言,指明当前位置位于字边界;这个位置根本不会被 \b 改变。这意味着永远不应重复零宽度断言,因为如果它们在给定位置匹配一次,它们显然可以无限次匹配。

简单来说,零宽断言就是匹配一个位置,这个位置满足某个正则,但是不纳入匹配结果的,所以叫“零宽”,而且这个位置的前面或后面需要满足某种正则。

除了匹配位置的正则外,后面要说的管道匹配符 | 也属于零宽断言,因为它只表示两个正则的“逻辑或”,自身不匹配任何字符。

基本位置

  • \b 匹配一个单词边界,也就是指单词和空格间的位置。
    • 例如,er\b 可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。
  • \B\b 相反,匹配非单词边界。er\B 能匹配“verb”中的“er”,但不能匹配“never”中的“er”。
  • ^ 匹配字符串的开头
    • 例如,^abc 可以匹配“abcx”,但是不能匹配“xabc”,因为后者开头是 “x”,而 ^abc 要求是以 “a” 开头。
  • $ 匹配字符串的结尾
    • 例如,abc$ 可以匹配“xabc”,但是不能匹配“abcx”,因为后者结尾是 “x”,而 abc$ 要求是以 “c” 结尾。
  • \A:仅匹配字符串的开头。 当不在 MULTILINE 模式时,\A 和 ^ 实际上是相同的。 在 MULTILINE 模式中,它们是不同的: \A 仍然只在字符串的开头匹配,但 ^ 可以匹配在换行符之后的字符串内的任何位置。
  • \Z:和 \A 相反,只匹配字符串尾。

特殊位置

有时候在使用正则表达式做匹配的时候,我们希望匹配一个字符串,这个字符串的前面或后面需要是特定的内容(这个内容比较复杂,不是简单的字符开始或结尾,而是需要用另一个正则来表示),这时候我们就需要用到前视断言和后视断言。

肯定型前视断言:(?=exp)

匹配一个位置(但结果不包含此位置)之前的文本内容,这个位置满足正则 exp,举例:匹配出字符串 string 中以 s 结尾的单词的前半部分

string = "Things are good! \nApples are fruits!"
re.findall(r'\b\w+(?=s\b)',string) # ['Thing', 'Apple', 'fruit']
否定型前视断言:(?!exp)

匹配一个位置(但结果不包含此位置)之前的文本,此位置不能满足正则 exp,举例:匹配出字符串 string 中不以 ing 结尾的单词的前半部分.负向断言不支持匹配不定长的表达式

string = 'done do doing'
re.findall(r'\b\w{3}(?!ing\b)',string) # ['don', 'doi']

string = 'done do doing'
re.findall(r'\b\w{2}(?!ing\b)',string) # ['do', 'do']

string = 'done do doing'
re.findall(r'\w{2}',string) # ['do', 'ne', 'do', 'do', 'in']
肯定型后视断言:(?<=exp)

匹配一个位置(但结果不包含此位置)之后的文本,这个位置满足正则 exp,举例:匹配出字符串 string 中以 h 开头的单词的后半部分.

string = 'hello, my honey! I think you are hungry!'
re.findall(r'(?<=\bh)\w+\b',string) # ['ello', 'oney', 'ungry']
否定型前视断言:(?<!exp)

匹配一个位置(但结果不包含此位置)之后的文本,这个位置不能满足正则 exp,举例:匹配字符串 s 中不以 do 开头的单词.

string = 'done do going'
re.findall(r'(?<!\bd)\w{5}\b',string) # ['going']

管道 |(逻辑或)

A|B, A 和 B 可以是任意正则表达式,创建一个正则表达式,匹配 A 或者 B. 任意个正则表达式可以用 '|' 连接。它也可以在组合内使用。

扫描目标字符串时, '|' 分隔开的正则样式从左到右进行匹配。当一个样式完全匹配时,这个分支就被接受。意思就是,一旦 A 匹配成功, B 就不再进行匹配,即便它能产生一个更好的匹配。或者说,'|' 操作符绝不贪婪。

如果要匹配 '|' 字符,使用 \|, 或者把它包含在字符集里,比如 [|].

例子:表达式 "Tom|Jack" 在匹配字符串 "I'mTom, he is Jack" 时,匹配结果是:成功;匹配到的内容是:"Tom";匹配到的位置是:开始于 4,结束于 7。如果要继续匹配下一个时,匹配结果是:成功;匹配到的内容是:"Jack";匹配到的位置时:开始于 15,结束于 19。

组合

普通组 (…)

匹配括号内的任意正则表达式,并标识出组合的开始和结尾。在被匹配次数修饰的时候,括号中的表达式可以作为整体被修饰;匹配完成后,组合的内容还可以被获取,并可以在之后用 \number 转义序列进行再次匹配

例如,表达式 (go\s*)+ 在匹配 “Let's go go go!” 时,匹配到内容是:“go go go”

命名组 (?P<name>…)

name 是该组的名称。命名组的行为与普通组完全相同,并且还将名称与组关联。这样后面在引用它们时,不仅可以像普通组中那样,通过数字去引用它们,还可以通过给的名称引用它们。

p = re.compile(r'(?P<word>\b\w+\b)')
m = p.search( '(((( Lots of punctuation )))' )
m.group('word')

m.group(1)

非捕获组 (?:…)

普通组和命名组都属于捕获组,即它们会“捕获”并“记下”匹配到的内容。但是有时你只想使用“组”来把正则表达式的某个部分括起来(显得清晰整洁),但是对检索组的内容不感兴趣。 这种情况下,可以通过使用非捕获组来达到相应目的。

非捕获组所匹配的子字符串 * 不能 * 在执行匹配后被获取或是之后在 pattern 中被引用

贪婪

默认条件下,正则匹配操作是遵循贪婪模式的 。

例如,当通过 *+ 等正则 pattern 进行重复正则时,匹配引擎将尝试重复尽可能多的次数。 如果表达式的后续部分不匹配,则匹配引擎将回退并以较少的重复次数再次尝试。

通过一个逐步示例更容易理解这一点。让我们分析一下表达式 a[bcd]*b 。 该表达式首先匹配一个字母 'a' ,接着匹配字符类 [bcd] 中的零个或更多个字母,最后以一个 'b' 结尾。 现在想象一下用这个正则来匹配字符串 'abcbd' 。

步骤 匹配 说明
1 a 正则中的 a 匹配成功。
2 abcbd 引擎尽可能多地匹配 [bcd]* ,直至字符串末尾。
3 失败 引擎尝试匹配 b ,但是当前位置位于字符串末尾,所以匹配失败。
4 abcb 回退,让 [bcd]* 少匹配一个字符。
5 失败 再次尝试匹配 b , 但是当前位置上的字符是最后一个字符 'd' 。
6 abc 再次回退,让 [bcd]* 只匹配 bc 。
6 abcb 再次尝试匹配 b 。 这一次当前位置的字符是 'b' ,所以它成功了。

此时正则表达式已经到达了尽头,并且匹配到了 'abcb' 。 这个例子演示了匹配引擎一开始会尽其所能地进行匹配,如果没有找到匹配,它将逐步回退并重试正则的剩余部分,如此往复,直至 [bcd]* 只匹配零次。如果随后的匹配还是失败了,那么引擎会宣告整个正则表达式与字符串匹配失败。

如果要变成非贪婪模式,可以在 "*","?","+","{m,n}" 后面加上 "?"

例如,a.*b 匹配 'aabcbcd' 时,在默认的贪婪模式下,匹配到的内容为 'aabcb';而如果改用非贪婪模式的 a.*?b 匹配 'aabcbcd' 时,匹配到的内容为 'aab'

其他

扩展标记法/内联标记

(?…) 是个扩展标记法。 '?' 后面的第一个字符决定了这个构建采用什么样的语法。这种扩展通常并不创建新的组合((?P<name>…) 是唯一的例外),只是表示一些特殊的匹配规则,可以代替后文 re.match()re.search() 等正则匹配方法和正则编译方法 re.compile() 中的 flags 参数的功能(相当于把这个参数直接写到正则 pattern 里面去)。 以下是目前支持的扩展:

  • (?aiLmsux:…)
    • 'a''i''L''m''s''u''x' 中的一个或多个) 这个组合匹配一个空字符串;这些字符对正则表达式设置以下标记 re.A (只匹配 ASCII 字符), re.I (忽略大小写), re.L (语言依赖), re.M (多行模式), re.S (点 dot 匹配全部字符), re.U (Unicode 匹配), and re.X (冗长模式),详见 文档 中的描述。 如果你想将这些标记包含在正则表达式中,这个方法就很有用,免去了在 re.compile() 中传递 flag 参数。标记应该在表达式字符串首位表示。
    • 例如 re.compile(r'(?i:ab)c') 意思是“ab”部分忽略大小写,所以可以匹配“Abc”、“ABc”等,但是不能匹配“abC”,因为“c”不在括号范围内。
  • (?aiLmsux-imsx:…)
    • ('a''i''L''m''s''u''x' 中的 0 或者多个, 之后可选跟随 '-' 在后面跟随 'i' , 'm' , 's' , 'x' 中的一到多个 .) 这些字符为表达式的其中一部分 设置 或者 去除 相应标记 re.A (只匹配 ASCII), re.I (忽略大小写), re.L (语言依赖), re.M (多行), re.S (点匹配所有字符), re.U (Unicode 匹配), and re.X (冗长模式),详见 文档 中的描述。
    • 'a''L' and 'u' 作为内联标记是相互排斥的, 所以它们不能结合在一起,或者跟随 '-' 。 当他们中的某个出现在内联组中,它就覆盖了括号组内的匹配 pattern。在 Unicode 样式中, (?a:…) 切换为 只匹配 ASCII, (?u:…) 切换为 Unicode 匹配 (默认). 在 byte 样式中 (?L:…) 切换为语言依赖模式, (?a:…) 切换为 只匹配 ASCII (默认)。这种方式只覆盖组合内匹配,括号外的匹配 pattern 不受影响。

注意事项

  • 反斜杠灾难
    • 正则表达式里使用 \ 作为转义字符,假如你需要匹配文本中的字符 \,那么使用编程语言表示的正则表达式里将需要 4 个反斜杠 \\\\:前两个和后两个分别用于在编程语言里转义成反斜杠,转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。
    • 可以使用原生字符串解决这个问题,Python 中字符串前⾯加上 r 表示原⽣字符串,原生字符串里面的 \ 不需要转义,就表示它自身。这样就只需要考虑正则 pattern 里面 \ 的表达方式,即用 2 个反斜杠 \\ 就可以了。

正则表达式方法

re 模块定义了几个函数,属性,和一个异常。绝大部分重要的应用,总是会先将正则表达式编译,之后在进行操作。

基本匹配

从字符串开头开始匹配 re.match(pattern, string, flags=0)

如果 string 开始的 0 或者多个字符匹配到了正则表达式样式,就返回一个相应的匹配对象。 如果没有匹配,就返回 None ;注意它跟零长度匹配是不同的。

注意即便是 MULTILINE 多行模式, re.match() 也只匹配字符串的开始位置,而不匹配每行开始。

如果你想定位 string 的任何位置,使用 search() 来替代(也可参考 search() vs. match() )

从字符串任意位置开始匹配 re.search(pattern, string, flags=0)

扫描整个 字符串 找到匹配样式的第一个位置,并返回一个相应的 匹配对象。如果没有匹配,就返回一个 None ; 注意这和找到一个零长度匹配是不同的。

search() vs. match()

Python 提供了两种不同的操作:基于 re.match() 检查字符串开头,或者 re.search() 检查字符串的任意位置,例如

re.match("c", "abcdef")    # No match
re.search("c", "abcdef")   # Match
<re.Match object; span=(2, 3), match='c'>

在 search() 中,可以用 '^' 作为开始来限制匹配到字符串的首位

re.match("c", "abcdef")    # No match
re.search("^c", "abcdef")  # No match
re.search("^a", "abcdef")  # Match

注意 MULTILINE 多行模式中函数 match() 只匹配字符串的开始,但使用 search() 和以 '^' 开始的正则表达式会匹配每行的开始

re.match('X', 'A\nB\nX', re.MULTILINE)  # No match
re.search('^X', 'A\nB\nX', re.MULTILINE)  # Match

匹配整个字符串 re.fullmatch(pattern, string, flags=0)

如果整个 string 完全匹配到正则表达式样式,就返回一个相应的匹配对象。 否则就返回一个 None ;注意这跟零长度匹配是不同的。

找到所有匹配项 re.findall(pattern, string, flags=0)

对 string 返回一个不重复的 pattern 的匹配列表, string 从左到右进行扫描,匹配按找到的顺序返回。如果样式里存在一到多个组,就返回一个组合列表;就是一个元组的列表(如果样式里有超过一个组合的话)。空匹配也会包含在结果里。

以迭代器的形式返回所有匹配项 re.finditer(pattern, string, flags=0)

pattern 在 string 里所有的非重复匹配,返回为一个迭代器 iterator,内容是匹配对象。 string 从左到右扫描,匹配按顺序排列。空匹配也包含在结果里。

匹配后编辑

匹配后分割字符串 re.split(pattern, string, maxsplit=0, flags=0)

>>> re.split(r'\W+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split(r'(\W+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split(r'\W+', 'Words, words, words.', 1)
['Words', 'words, words.']
>>> re.split('[a-f]+', '0a3B9', flags=re.IGNORECASE)
['0', '3', '9']

如果分隔符里有捕获组合,并且匹配到字符串的开始,那么结果将会以一个空字符串开始。对于结尾也是一样

>>> re.split(r'(\W+)', '…words, words…')
['', '…', 'words', ', ', 'words', '…', '']

这样的话,分隔组将会出现在结果列表中同样的位置。样式的空匹配仅在与前一个空匹配不相邻时才会拆分字符串。

re.split(r'\b', 'Words, words, words.')
['', 'Words', ', ', 'words', ', ', 'words', '.']
re.split(r'\W*', '…words…')
['', '', 'w', 'o', 'r', 'd', 's', '', '']
re.split(r'(\W*)', '…words…')
['', '…', '', '', 'w', '', 'o', '', 'r', '', 'd', '', 's', '…', '', '', '']

匹配后替换字符串 re.sub(pattern, repl, string, count=0, flags=0)

返回通过使用 repl 替换在 string 最左边非重叠出现的 pattern 而获得的字符串。 如果样式没有找到,则不加改变地返回 string。 repl 可以是字符串或函数;如为字符串,则其中任何反斜杠转义序列都会被处理。 也就是说,\n 会被转换为一个换行符,\r 会被转换为一个回车符,依此类推。 未知的 ASCII 字符转义序列保留在未来使用,会被当作错误来处理。 其他未知转义序列例如 \& 会保持原样。 向后引用像是 \6 会用样式中第 6 组所匹配到的子字符串来替换。 例如:

>>> re.sub(r'def\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*\(\s*\):',
...        r'static PyObject*\npy_\1(void)\n{',
...        'def myfunc():')
'static PyObject*\npy_myfunc(void)\n{'

如果 repl 是一个函数,那它会对每个非重复的 pattern 的情况调用。这个函数只能有一个匹配对象参数,并返回一个替换后的字符串。比如

>>> def dashrepl(matchobj):
...     if matchobj.group(0) == '-': return ' '
...     else: return '-'
>>> re.sub('-{1,2}', dashrepl, 'pro----gram-files')
'pro--gram files'
>>> re.sub(r'\sAND\s', ' & ', 'Baked Beans And Spam', flags=re.IGNORECASE)
'Baked Beans & Spam'

样式可以是一个字符串或者一个正则表达式对象 。

可选参数 count 是要替换的最大次数;count 必须是非负整数。如果省略这个参数或设为 0,所有的匹配都会被替换。 样式的空匹配仅在与前一个空匹配不相邻时才会被替换,所以 sub('x*', '-', 'abxd') 返回 '-a-b--d-' 。

在字符串类型的 repl 参数里,如上所述的转义和向后引用中,\g<name> 会使用命名组合 name,(在 (?P<name>…) 语法中定义) \g<number> 会使用数字组;\g<2> 就是 \2,但它避免了二义性,如 \g<2>0。 \20 就会被解释为组 20,而不是组 2 后面跟随一个字符 '0'。向后引用 \g<0> 把 pattern 作为一整个组进行引用。

re.subn(pattern, repl, string, count=0, flags=0),行为与 sub() 相同,但是返回一个元组 (字符串, 替换次数).

正则编译

正则 pattern 编译 re.compile(pattern, flags=0)

将正则表达式的 pattern 编译为一个 正则表达式对象 (正则对象),可以通过这个对象的方法,如 match()search() 等,对字符串进行匹配。

如果需要多次使用这个正则表达式的话,使用 re.compile() 编译保存这个正则对象以便复用,可以让程序更加高效。

通过 re.compile() 编译后的样式,和模块级的函数会被缓存, 所以少数的正则表达式使用无需考虑编译的问题。

prog = re.compile(pattern)
result = prog.match(string)

# 等价于
result = re.match(pattern, string)

这个表达式的行为可以通过指定 flag 的值来改变(也可以直接在正则 pattern 中通过指定特定内联标记来改变)。值可以是以下任意变量,可以通过“位或”操作来结合( | 操作符)多个变量。

  • re.A/re.ASCII/内联标记 (?a)

让 \w\W\b\B\d\D\s 和 \S 只匹配 ASCII,而不是 Unicode。这只对 Unicode 样式有效,会被 byte 样式忽略。相当于前面语法中的 。

注意,为了保持向后兼容, re.U 标记依然存在(还有他的同义 re.UNICODE 和嵌入形式 (?u) ) , 但是这些在 Python 3 是冗余的,因为默认字符串已经是 Unicode 了(并且 Unicode 匹配不允许 byte 出现)。

  • re.DEBUG: 显示编译时的 debug 信息,没有内联标记。

  • re.I/re.IGNORECASE/内联标记 (?i)

进行忽略大小写匹配;表达式如 [A-Z] 也会匹配小写字符。Unicode 匹配(比如 Ü 匹配 ü)同样有用,除非设置了 re.ASCII 标记来禁用非 ASCII 匹配。当前语言区域不会改变这个标记,除非设置了 re.LOCALE 标记。

注意,当设置了 IGNORECASE 标记,搜索 Unicode 样式 [a-z] 或 [A-Z] 的结合时,它将会匹配 52 个 ASCII 字符和 4 个额外的非 ASCII 字符: 'İ' (U+0130, 拉丁大写的 I 带个点在上面), 'ı' (U+0131, 拉丁小写没有点的 I ), 'ſ' (U+017F, 拉丁小写长 s) and 'K' (U+212A, 开尔文符号).如果使用 ASCII 标记,就只匹配 'a' 到 'z' 和 'A' 到 'Z' 。

  • re.L/re.LOCALE/内联标记 (?L)

由当前语言区域决定 \w\W\b\B 和大小写敏感匹配。这个标记只能对 byte 样式有效。这个标记不推荐使用,因为语言区域机制很不可靠,它一次只能处理一个 " 习惯”,而且只对 8 位字节有效。Unicode 匹配在 Python 3 里默认启用,并可以处理不同语言。

  • re.M/re.MULTILINE/内联标记 (?m)

设置以后,样式字符 '^' 匹配字符串的开始,和每一行的开始(换行符后面紧跟的符号);样式字符 '$' 匹配字符串尾,和每一行的结尾(换行符前面那个符号)。默认情况下,’^’ 匹配字符串头,'$' 匹配字符串尾 。

  • re.S/re.DOTALL/内联标记 (?s)

让 '.' 特殊字符匹配任何字符,包括换行符;如果没有这个标记,'.' 就匹配 除了 换行符的其他任意字符。

  • re.X/re.VERBOSE/内联标记 (?x)

这个标记允许你编写更具可读性更友好的正则表达式。通过分段和添加注释。空白符号会被忽略,除非在一个字符集合当中或者由反斜杠转义,或者在 *?(?: or (?P<…> 分组之内。当一个行内有 # 不在字符集和转义序列,那么它之后的所有字符都是注释。

意思就是下面两个正则表达式等价地匹配一个十进制数字:

a = re.compile(r"""\d +  # the integral part
                   \.    # the decimal point
                   \d *  # some fractional digits""", re.X)
b = re.compile(r"\d+\.\d*")

正则表达式对象支持的方法和属性

编译得到的正则表达式对象支持的方法和属性,和基本正则匹配中的相应函数类似,但是参数略有差异

  • Pattern.match(string[, pos[, endpos]])
    • 如果 string 的 开始位置 能够找到这个正则样式的任意个匹配,就返回一个相应的 匹配对象。如果不匹配,就返回 None ;注意它与零长度匹配是不同的。
    • 可选参数 pos 和 endpos 与 search() 含义相同。
>>> pattern = re.compile("o")
>>> pattern.match("dog")      # No match as "o" is not at the start of "dog".
>>> pattern.match("dog", 1)   # Match as "o" is the 2nd character of "dog".
<re.Match object; span=(1, 2), match='o'>
  • Pattern.search(string[, pos[, endpos]])
    • 扫描整个 string 寻找第一个匹配的位置, 并返回一个相应的 匹配对象。如果没有匹配,就返回 None
    • 可选参数 pos 给出了字符串中开始搜索的位置索引;默认为 0,它不完全等价于字符串切片; '^' 样式字符匹配字符串真正的开头,和换行符后面的第一个字符,但不会匹配索引规定开始的位置。
    • 可选参数 endpos 限定了字符串搜索的结束;它假定字符串长度到 endpos , 所以只有从 pos 到 endpos - 1 的字符会被匹配。如果 endpos 小于 pos,就不会有匹配产生;另外,如果 rx 是一个编译后的正则对象, rx.search(string, 0, 50) 等价于 rx.search(string[:50], 0)
>>> pattern = re.compile("d")
>>> pattern.search("dog")     # Match at index 0
<re.Match object; span=(0, 1), match='d'>
>>> pattern.search("dog", 1)  # No match; search doesn't include the "d"
  • Pattern.fullmatch(string[, pos[, endpos]])
    • 如果整个 string 匹配这个正则表达式,就返回一个相应的匹配对象。 否则就返回 None ; 注意跟零长度匹配是不同的。
    • 可选参数 pos 和 endpos 与 search() 含义相同。
>>> pattern = re.compile("o[gh]")
>>> pattern.fullmatch("dog")      # No match as "o" is not at the start of "dog".
>>> pattern.fullmatch("ogre")     # No match as not the full string matches.
>>> pattern.fullmatch("doggie", 1, 3)   # Matches within given limits.
<re.Match object; span=(1, 3), match='og'>
  • Pattern.split(string, maxsplit=0) 等价于 split() 函数,使用了编译后样式
  • Pattern.findall(string[, pos[, endpos]]) 类似函数 findall() , 使用了编译后样式,但也可以接收可选参数 pos 和 endpos ,限制搜索范围
  • Pattern.finditer(string[, pos[, endpos]]) 类似函数 finiter() , 使用了编译后样式,但也可以接收可选参数 pos 和 endpos ,限制搜索范围
  • Pattern.sub(repl, string, count=0) 等价于 sub() 函数,使用了编译后的样式。
  • Pattern.subn(repl, string, count=0) 等价于 subn() 函数,使用了编译后的样式。
  • Pattern.flags 正则匹配标记。这是可以传递给 compile() 的参数,任何 (?…) 内联标记,隐性标记比如 UNICODE 的结合。
  • Pattern.groups 正则 pattern 中捕获组的数量。
  • Pattern.groupindex 命名组的名字和组号构成的字典,如 {'gname1':1, 'gname2':2}。如果没有命名组,那字典就是空的。
  • Pattern.pattern 编译对象的原始样式字符串。

匹配对象

如果正则表达式成功匹配到了字符串,就会返回一个“匹配对象”(对应的布尔值为 True),反之会返回 None(对应的布尔值是 False)

匹配对象支持以下方法和属性:

方法

  • Match.expand(template)
    • 对 template 进行反斜杠转义替换并且返回,就像 sub() 方法中一样。转义如同 \n 被转换成合适的字符,数字引用 (\1\2) 和命名组合 (\g<1>\g<name>) 替换为相应组合的内容。
  • Match.group([group1, …])
    • 根据输入的组号,返回一个或者多个匹配的子组。
    • 组号意义
      • 组号为 0,表示整个正则 pattern 匹配到的内容
      • 组号为 [1..99] 的正整数,表示正则 pattern 里面相应的组匹配到的内容
      • 对于命名组, 组号也可能是命名组的名字。
      • 如果一个组被匹配多次,就返回最后一个匹配结果
      • 组号为负数,或者大于样式中定义的组数,或者是未定义的命名组名称,会抛出一个 IndexError
    • 参数可能情况
      • 如果没有参数,等价于参数为 0,结果是一个字符串,表示整个正则 pattern 匹配的内容
      • 如果只有一个参数(组号),结果是一个字符串,表示该组匹配的内容
      • 如果有多个参数(组号),结果是一个元组(每个参数对应一个项),表示对应组匹配的内容。
>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
>>> m.group(0)       # The entire match
'Isaac Newton'
>>> m.group(1)       # The first parenthesized subgroup.
'Isaac'
>>> m.group(2)       # The second parenthesized subgroup.
'Newton'
>>> m.group(1, 2)    # Multiple arguments give us a tuple.
('Isaac', 'Newton')

一个相对复杂的例子

>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")
>>> m.group('first_name')
'Malcolm'
>>> m.group('last_name')
'Reynolds'

# 命名组合同样可以通过索引值引用
>>> m.group(1)
'Malcolm'
>>> m.group(2)
'Reynolds'

如果一个组匹配成功多次,就只返回最后一个匹配

>>> m = re.match(r"(..)+", "a1b2c3")  # Matches 3 times.
>>> m.group(1)                        # Returns only the last match.
'c3'
  • Match.__getitem__(g) 即直接用方括号索引
    • 等价于 m.group(g)。这允许更方便的引用一个匹配
>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
>>> m[0]       # The entire match
'Isaac Newton'
>>> m[1]       # The first parenthesized subgroup.
'Isaac'
>>> m[2]       # The second parenthesized subgroup.
'Newton'
  • Match.groups(default=None)
    • 返回一个元组,包含在正则 pattern 中出现的从 1 到任意多的组合。 default 参数用于指定未参与匹配的组的返回值,默认为 None
>>> m = re.match(r"(\d+)\.(\d+)", "24.1632")
>>> m.groups()
('24', '1632')

>>> m = re.match(r"(\d+)\.?(\d+)?", "24") # second group is not matched
>>> m.groups()      # Second group defaults to None.
('24', None)
>>> m.groups('0')   # Now, the second group defaults to '0'.
('24', '0')
  • Match.groupdict(default=None)
    • 返回一个字典,包含了所有的 命名 子组。key 就是组名。 default 参数用于不参与匹配的组合;默认为 None。 例如
>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")
>>> m.groupdict()
{'first_name': 'Malcolm', 'last_name': 'Reynolds'}
  • Match.start([group]) / Match.end([group])
    • 返回 group 匹配到的字串的开始和结束 index。group 默认为 0(意思是整个匹配的子串)。
    • 如果 group 存在,但未产生匹配,就返回 -1 。
    • 如果 group 存在,并产生匹配 ,则它匹配的内容 (等价于 m.group(group)) 是 m.string[m.start(g):m.end(g)]
    • 如果 group 匹配到一个空字符串的话, m.start(group) 将会等于 m.end(group)
      一个应用的例子
>>> email = "tony@tiremove_thisger.net"
>>> m = re.search("remove_this", email)
>>> email[:m.start()] + email[m.end():]
'tony@tiger.net
  • Match.span([group])
    • 对于一个匹配 m , 返回一个二元组 (m.start(group), m.end(group)) 。 注意如果 group 没有在这个匹配中,就返回 (-1, -1) 。group 默认为 0,就是整个匹配。

属性

  • Match.pos / Match.endpos 属性
    • 传递给正则对象的 search() 或 match() 方法的 posendpos 参数的值,表示正则引擎在字符串中进行搜索的开始和结束位置。
  • Match.lastindex 属性
    • 最后一个匹配到的捕获组的索引值,如果没有匹配产生的话,返回 None 。比如,对于字符串 'ab',表达式 (a)b((a)(b)), 和 ((ab)) 将得到 lastindex == 1 , 而 (a)(b) 会得到 lastindex == 2 。
  • Match.lastgroup
    • 最后一个匹配到的捕获组的名字,如果它没有名字(不是命名组)或者完全没有产生匹配,返回 None
  • Match.re / Match.string
    • 通过 match() 或 search() 方法产生Match这个匹配对象的正则对象和传入的字符串。即 Match = RE.match(STR)Match = RE.search(STR),里面的 RE 和 STR 的值。

其他

转移正则 pattern 中的元字符 re.escape(pattern)

如果你想对任意可能包含正则表达式元字符的文本字符串进行匹配,它就是有用的。比如

>>> print(re.escape('http://www.python.org'))
http://www\.python\.org

>>> legal_chars = string.ascii_lowercase + string.digits + "!#$%&'*+-.^_`|~:"
>>> print('[%s]+' % re.escape(legal_chars))
[abcdefghijklmnopqrstuvwxyz0123456789!\#\$%\&'\*\+\-\.\^_`\|\~:]+

>>> operators = ['+', '-', '*', '/', '**']
>>> print('|'.join(map(re.escape, sorted(operators, reverse=True))))
/|\-|\+|\*\*|\*

这个函数不能被用于 sub() 和 subn() 的替换字符串,只有反斜杠应该被转义。 例如:

>>> digits_re = r'\d+'
>>> sample = '/usr/sbin/sendmail - 0 errors, 12 warnings'
>>> print(re.sub(digits_re, digits_re.replace('\\', r'\\'), sample))
/usr/sbin/sendmail - \d+ errors, \d+ warnings

清除正则表达式的缓存 re.purge()

注意事项

  • 关于 findall 的神奇定义 ? ?

正则表达式示例

  • 字符串 ip 是一个 ip 地址,匹配出其中的四个整数

    • r'(?<=\.)?\d+(?=\.)?',ip) 
  • 汉字

    • re.compile(r"[\u4e00-\u9fa5]")
  • 校验数字的相关表达式

功能 表达式
数字 `[1]*
------------------------------ ----------------------------------
n 位的数字 `^\d
------------------------------ ----------------------------------
至少 n 位的数字 `^\d
------------------------------ ----------------------------------
                       |

| m-n 位的数字 | ^\d{m,n}| 功能 | 表达式 | | ------------------------------ | ---------------------------------- | | | 零和非零开头的数字 | ^(0|[1-9][0-9])| 功能 | 表达式 |
| ------------------------------ | ---------------------------------- |
|
| 非零开头的最多带两位小数的数字 | ^([1-9][0-9]*)+(.[0-9]{1,2})?| 功能 | 表达式 | | ------------------------------ | ---------------------------------- | | | 带 1-2 位小数的正数或负数 | ^(-)?\d+(.\d{1,2})?| 功能 | 表达式 |
| ------------------------------ | ---------------------------------- |
|
| 正数、负数、和小数 | ^(\-\|\+)?\d+(\.\d+)?| 功能 | 表达式 | | ------------------------------ | ---------------------------------- | | | 有两位小数的正实数 | [2]+(.[0-9]{2})?| 功能 | 表达式 |
| ------------------------------ | ---------------------------------- |
|
| 有 1~3 位小数的正实数 | ^[0-9]+(.[0-9]{1,3})?| 功能 | 表达式 | | ------------------------------ | ---------------------------------- | | | 非零的正整数 | [3]\d
| 功能 | 表达式 |
| ------------------------------ | ---------------------------------- |
|
| 非零的负整数 | ^-[1-9]\d*| 功能 | 表达式 | | ------------------------------ | ---------------------------------- | | | 非负整数 | ^\d+| 功能 | 表达式 |
| ------------------------------ | ---------------------------------- |
|
| 非正整数 | ^-[1-9]\d*\|0| 功能 | 表达式 | | ------------------------------ | ---------------------------------- | | | 非负浮点数 | ^\d+(.\d+)?| 功能 | 表达式 |
| ------------------------------ | ---------------------------------- |
|
| 非正浮点数 | ^((-\d+(\.\d+)?)\|(0+(\.0+)?))| 功能 | 表达式 | | ------------------------------ | ---------------------------------- | | | 正浮点数 | [4]\d.\d|0.\d[1-9]\d| 功能 | 表达式 |
| ------------------------------ | ---------------------------------- |
|
| 负浮点数 | ^-([1-9]\d*\.\d*\|0\.\d*[1-9]\d*)| 功能 | 表达式 | | ------------------------------ | ---------------------------------- | | | 浮点数 | ^(-?\d+)(.\d+)?| 功能 | 表达式 |
| ------------------------------ | ---------------------------------- |
|

  • 校验字符的相关表达式
功能 表达式
汉字 `[5]
--- ---
英文和数字 `[6]+
--- ---
长度为 3-20 的所有字符 `^.
--- ---
由 26 个英文字母组成的字符串 `[7]+
--- ---
由 26 个大写英文字母组成的字符串 `[8]+
--- ---
由 26 个小写英文字母组成的字符串 `[9]+
--- ---
由数字和 26 个英文字母组成的字符串 `[10]+
--- ---
由数字、26 个英文字母或者下划线组成的字符串 `^\w+
--- ---
中文、英文、数字包括下划线 `[11]+
--- ---
  • 特殊场景的表达式
功能 表达式
Email地址 `^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*
-------------------------------- -------------------------------------------------------------------------------------------------------------
  |

| InternetURL | [a-zA-z]+://[^\s]* 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?| 功能 | 表达式 | | -------------------------------- | ------------------------------------------------------------------------------------------------------------- | | | 手机号码 | ^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}| 功能 | 表达式 |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
| 国内电话号码 | \d{3}-\d{8}\|\d{4}-\d{7}(0511-4405222、021-87888822) |
| 身份证号 | ^\d{15}\|\d{18}$(15 位、18 位数字) |
| 短身份证号码 | ^([0-9]){7,18}(x\|X)?$ 或 ^\d{8,18}\|[0-9x]{8,18}\|[0-9X]{8,18}?$(数字、字母 x 结尾) |
| 帐号是否合法 | ^[a-zA-Z][a-zA-Z0-9_]{4,15}$(字母开头,允许 5-16 字节,允许字母数字下划线) |
| 密码 | ^[a-zA-Z]\w{5,17}$(以字母开头,长度在 6~18 之间,只能包含字母、数字和下划线) |
| 强密码 | ^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在 8-10 之间) |
| 日期格式 | ^\d{4}-\d{1,2}-\d{1,2} |
| 一年的 12 个月 (01~09 和 1~12) | ^(0?[1-9]\|1[0-2])$ |
| 一个月的 31 天 (01~09 和 1~31) | ^((0?[1-9])\|((1\|2)[0-9])\|30\|31)$ |
| xml 文件 | ^([a-zA-Z]+-?)+[a-zA-Z0-9]+\\.[x\|X][m\|M][l\|L]$ |
| 双字节字符 | [^\x00-\xff](包括汉字在内,可以用来计算字符串的长度 (一个双字节字符长度计 2,ASCII 字符计 1)) |
| 空白行的正则表达式 | \n\s*\r (可以用来删除空白行) |
| HTML 标记的正则表达式 | <(\S*?)[^>]*>.*?</\1>\|<.*? />(对于复杂的嵌套标记依旧无能为力) |
| 首尾空白字符的正则表达式 | ^\s*\|\s*$或(^\s*)\|(\s*$)(可以用来删除行首行尾的空白字符 (包括空格、制表符、换页符等等)) |
| 腾讯 QQ 号 | [1-9][0-9]{4,} (腾讯 QQ 号从 10000 开始) |
| 中国邮政编码 | [1-9]\d{5}(?!\d) (中国邮政编码为 6 位数字) |
| IP 地址提取 | \d+\.\d+\.\d+\.\d+ (提取 IP 地址时有用) |
| IP 地址合法性判断 | ((?:(?:25[0-5]\|2[0-4]\\d\|[01]?\\d?\\d)\\.){3}(?:25[0-5]\|2[0-4]\\d\|[01]?\\d?\\d)) |

Tips

  • 第三方模块 regex , 提供了与标准库 re 模块兼容的 API 接口, 同时还提供了额外的功能和更全面的 Unicode 支持。

参考


  1. 0-9 ↩︎

  2. 0-9 ↩︎

  3. 1-9 ↩︎

  4. 1-9 ↩︎

  5. \u4e00-\u9fa5 ↩︎

  6. A-Za-z0-9 ↩︎

  7. A-Za-z ↩︎

  8. A-Z ↩︎

  9. a-z ↩︎

  10. A-Za-z0-9 ↩︎

  11. \u4E00-\u9FA5A-Za-z0-9_ ↩︎