Python 正则表达式

如果已经掌握正则了,直接拉到最下面,看简略表格。

简单正则表达式

正则表达式,是使用字符串表达的某种模式,可以用于匹配某一类字符串。

比如 abcd 作为一个正则表达式可以匹配 ffabcdef 中的 abcd。

像上面一样,多数字符只能匹配它本身,比如 正则表达式中的 a 匹配目标字符串中的 a, b 匹配 b 。这种情况下,类似于字符串搜索。 但是正则表达式比字符串搜索强大的地方在于提供了一些特殊字符,进行模式匹配。

  1. .

    英文的点 . 。可以用于匹配任意字符,

    • h.llo 可以匹配 hello, hfllo, hgllo …
    • h..lo 可以匹配 hello, hablo, hxylo …
  2. *

    星号 * ,并不是用来代指字符,而是说前面一个字符可以出现任意多次,即出现次数大于等于0 . 并且 * 可以和 . 组合使用。

    • ap*le 可以匹配 ale, aple, apple, appple, …
    • a.*le 可以匹配 ale, azzzzzzzle, abbbble, aaaaaaaaale, …
  3. +

    +* 很像,含义是 + 前面的字符出现次数大于等于1,和 * 的区别在于,它肯定至少出现一次。

    • ap+le 可以匹配 aple, apple, apppppppple, 但是不能匹配 ale,
    • a.+le 可以匹配 azle, azzzzzzzzle, abbbbbble, 不能匹配 ale。
  4. ?

    ? 表示前面的字符可以出现 0 ~ 1 次,也就是说可以出现或者不出现。

    • app?le 可以匹配 aple 和 apple
    • ap.?le 可以匹配 aple, apple, apale, apble, …
  5. \

    前面提到过几种特殊字符 .*+? , 当需要匹配到字符串中的 * ,而不希望将它当成正则模式时, 可以使用 \ 将其转义为普通字符。 \ 也可以用来转义 \ 本身

    • app\?le 匹配 app?le , 不能匹配 apple 和 aple .
    • app\\le 可以匹配 app\le
    • app\\?le 可以匹配 app\leapple
  6. [ ]

    方括号用来包含一组字符,当成一个字符来使用。

    . 可以理解为 [ ] 的特殊版本, . 可以匹配任意字符, [ ] 可以匹配指定字符, 指定的字符填写在方括号内部,如果将所有字符都填写在方括号内部,作用就变成了匹配任意字符。

    • [abc] 可以匹配 a, b, c
    • h[abc]llo 可以匹配 hallo, hbllo, hcllo 不能匹配 habllo habcllo

    需要注意的是, 方括号中可以不用 \ 转义直接写 . * + ? | ^ $ ( ) [ { } 这些特殊字符。 只有 ] \ 两个字符除外。只不过,加上转义符也是没有关系的。 所以,如果记不住的话,还是每次都加上转义符吧,以免出错。

    • h[*?.]llo 可以匹配 h*llo, h?llo, h.llo。
    • h[\*\?\.]lloh[*?.]llo 是等价的

    方括号也可以和 * + ? 配合使用。

    可以使用 - 表示区间,如果 ^ 出现在首个字符,表示取反。

    • [a-z] 表示 abcd…xyz 的所有小写字母。
    • [^a-z] 表示除去 abcd…xyz 之外的所有字符。
    • [a-zA-Z0-9] 表示 大写字母、小写字母、所有数字
    • [a-zA-Z0-9!@#] 表示 大写字母、小写字母、所有数字、!@#三个符号

    方括号括起来的字符本身是只能匹配一次的,可以和 *+? 结合使用匹配多次

    • p[abc]+ 可以匹配 pa, pb, pc, pab, pccc, pbcabbaaac …
  7. { }

    {}* + ? 的增强版,它可以指定前一个字符出现的次数。语法是 {出现最小次数, 最大次数}

    • * 等价于 {0, }
    • + 等价于 {1, }
    • ? 等价于 {0, 1}

    如果后面的数字不填写,就相当于无穷大, 如 {5, } 表示前一个字符出现至少5次。

    也可以只填写单个数字,如 {5} 等价于 {5, 5} ,

  8. ( )

    将一个模式进行分组,看成是单个模式,单独使用时,并没有实际作用,一般作为其他功能的铺垫。

  9. |

    表示 或,和 [] 类似,但是 [] 只能表示单字符之间的或的关系。

    chin(a)|(ese) 可以匹配 china, chinese

  10. ^

    匹配行首。

  11. $

    匹配行尾。

另外正则表达式中还有一些快捷代号:

符号 描述 等价形式
\d 匹配数字 [0-9]
\D 匹配非数字 [^0-9]
\s 匹配不可见字符,如空格 [ tnrfv]
\S 匹配任何非不可见字符 [^ tnrfv]
\w 匹配所有数字字母下划线 [a-zA-Z0-9_]
\W 匹配所有非数字字母下划线 [^a-zA-Z0-9_]

python re模块接口

正则表达式是使用字符串来表示的,如 b(an)+a .但是字符串并不是正则表达式, 需要经过编译后才能变成正则表达式对象。编译前,它仅仅是普通的字符串而已。

import re

# 两者是等价的
reg1 = re.compile('b(an)+a')
reg2 = re.compile('b' + '(an)+' + 'a')
# 编译后的reg1/reg2才是正则表达式。

# 使用match匹配字符串。
match = reg1.match("banana")

正则专用字符串

关于转义符,比如正则表达式 8\*8 , 使用了斜杠转义 * , 在普通字符串中,斜杠本身也是转义符, 所以需要写成 '8\\*8' , 需要写两遍,比较繁琐。

所以Python额外提供了一种正则表达式专用字符串:在普通字符串前加上r,字符串中的斜杠将不会被当做转义符。

看一下对比

普通字符串 正则字符串
"ab*" r"ab*"
"\\\\section" r"\\section"
"\\w+\\s+\\1" r"\w+\s+\1"

接口

match

从某个位置严格匹配一个字符串。通过第二个参数指定开始位置。默认是最开始位置。

匹配成功返回一个保存了位置信息的 Match 。 失败返回 None .

reg = re.compile("bc")
m = reg.match('bcdef') # m.group() -> bc。 成功
m = reg.match('abcdef) # m -> None 匹配失败

search

从某个位置开始搜索字符串。返回第一个搜索到的串。

m = reg.search('bcdef')

相当于顺序执行了以下表达式,但是,只要有一个匹配成功就提前返回:

text = "bcdef"
m = reg.match(text, 0)
if m:
    return m
m = reg.match(text, 1)
if m:
    return m
m = reg.match(text, 2)
if m:
    return m
…… ……
m = reg.match(text, len(text)-1)
if m:
    return m

搜索成功返回 Match , 失败返回None

findall

直接返回搜索到的字符串列表。

reg = re.compile(r'\d+')
reg.findall('the 3rd people, 34 years old.')
# Out: ['3', '34']

finditer

和search/findall类似,但是会一直迭代返回所有搜索到的子串, 每一个迭代值是一个 Match 。

In [7]: text = 'the 3rd people, 34 years old.'
In [9]: for span in reg.finditer(text):
   ...:     print(span)
   ...:
<_sre.SRE_Match object; span=(4, 5), match='3'>
<_sre.SRE_Match object; span=(16, 18), match='34'>

其他接口

fullmatch:字符串和正则表达式完全匹配。
split:和python自带split差不多,但是可以用正则分割字符串。
sub:和python自带replace差不多,使用正则替换字符串。
subn:和sub类似,替换字符串,指定最大替换次数。

正则高级用法-分组

正则表达式有分组的概念,前面提到过 ( ) 的语法, 单独使用可以将一个模式作为一个分组, 作为一个最小的不可分割单位。

(ab)+ 可以完全匹配 abababab, 不能完整匹配 abbbbb

分组还有很多其他用途。分组有两种: 命名分组、匿名分组。 所有分组都会被记录下来,会自动分配一个编号,从前往后分别为 1、 2、 3 ……, 可以通过编号引用匿名分组。

  1. 在结果中提取分组

    举例, 下面的正则表达式可以提取出来 多少个苹果交换了多少个香蕉。 其中苹果个数是第一个分组,香蕉个数是第二个分组。

    (\d) apple exchange (\d) banana
    
    In [1]: reg = re.compile(r'(\d) apple exchange (\d) banana')
    In [2]: m = reg.match('6 apple exchange 7 banana')
    In [3]: m
    Out[3]: <_sre.SRE_Match object; span=(0, 25), match='6 apple exchange 7 banana'>
    
    In [4]: m.groups()
    Out[4]: ('6', '7')
    

    上面的例子,可以通过 match.groups() 提取出所有分组的匹配结果。 这对于从字符串中抽取信息非常有用。

  2. 命名分组

    通过 (?P<name>...) 的方式可以创建命名分组。 结果也可以通过 match.groups() 获取, 同时命名分组还可以使用 match.groupdict() 提取分组匹配结果, 可读性更好。

    In [1]: reg = re.compile(r'(?P<apple_num>\d) apple exchange (?P<banana_num>\d) banana')
    
    In [2]: m = reg.match('6 apple exchange 7 banana')
    
    In [3]: m.groups()
    Out[3]: ('6', '7')
    
    In [4]: m.groupdict()
    Out[4]: {'apple_num': '6', 'banana_num': '7'}
    
  3. 正则表达式内部分组引用

    正则表达式内部可以使用 1, 2, 3 对特定编号的分组进行引用。

    比如上面的例子,要求 apple 和 banana 的数量必须相等。就可以使用下面的正则。

    reg = re.compile(r'(\d) apple exchange \1 banana') # 使用 \1 占位表示此处和第一个分组的内容一样。
    m = reg.match('6 apple exchange 7 banana') # => 匹配失败 因为 6/7 不一样
    m = reg.match('6 apple exchange 6 banana')
    # out: <_sre.SRE_Match object; span=(0, 25), match='6 apple exchange 6 banana'>
    m.groups() # => ('6',)
    

    对于命名分组的引用还可以使用另外一种可读性更好的形式: (?P=name)

    # 引用 apple_num
    reg = re.compile(r'(?P<apple_num>\d) apple exchange (?P=apple_num) banana')
    m = reg.match('6 apple exchange 7 banana') # => 匹配失败 因为 6/7 不一样
    m = reg.match('6 apple exchange 6 banana')
    # out: <_sre.SRE_Match object; span=(0, 25), match='6 apple exchange 6 banana'>
    m.groups() # => ('6',)
    m.groupdict() # => {'apple_num': '6'}
    

除了上面所说的,正则表达式还存在向前尝试匹配的行为。见下面的附表。

正则表达式附表

正则表达式 作用
. 匹配任意字符
^ 匹配开头
$ 匹配结尾
* 前一个字符出现任意次,也可以不出现
+ 前一个字符出现至少一次
? 前一个字符出现一次或者不出现
*?, +?, ??

默认情况下, * + ? 都是贪婪模式,在后面加一个 ? 将其转换为非贪婪模式,

即尽可能匹配更少字符。

{m} 前一个字符出现m次
{m,n} 前一个字符出现 m~n 次,此区间为闭区间。
{m,n}? 将此模式转换为非贪婪模式
\ 转义符
[] 匹配中括号中的字符
表示或的关系
() 一般用于创建分组
(?...)

单独使用并没有什么实际含义,根据 后的第一个字符决定这个表达式的含义,见下面。

一般来说除了 (?P<name>…) 之外,这类表达式都不会被作为分组

(?:...) 默认括号包含的模式都会变成分组,使用这个表达式将不会作为分组。
(?P<name>...) 创建命名分组
(?P=name) 引用命名分组
(?#...) 在正则表达式中增加注释,
(?=...)

试探后面的字符是否符合某种模式,不会消耗字符。

比如 \d+(?=th) 可以匹配 5th 中的 5,而不是 5th,

因为它仅仅是向前试探而不消耗th两个字符, 并且不能匹配 1st 中的 1。

(?!...) 试探后面的字符不符合某种模式。
(?<=...) 试探前面的字符是否符合某种模式
(?<!...) 试探前面的字符是否不符合某种模式
(?(id/name)yes-pattern∣no-pattern)

引用前面的分组,如果引用成功(即分组存在),

则当前使用 yes-pattern 中的模式匹配, 否则使用 no-pattern 中的模式匹配

\number 使用数字编号引用分组
\A 匹配字符串开头
\b

匹配单词开头结尾。

r'\bfoo\b' 可以匹配 ‘foo’, ‘foo.’, ‘(foo)’, ‘bar foo baz’,

但是不能匹配 ‘foobar’ or ‘foo3’

\B \b 相反,匹配单词的非开头结尾部分。
\d 匹配数字
\D 匹配非数字
\s 匹配不可见字符,如空格
\S 匹配任何非不可见字符
\w 匹配所有数字字母下划线
\W 匹配所有非数字字母下划线