Python和正则匹配

正则匹配

预定义字符集

利用\转移字符加上字母可以表示通常使用的预定义字符集,下面是常用的预定义字符集:

  • \w(word):匹配字母、数字、下划线。等价于 [A-Za-z0-9_]
  • \W:匹配非字母、非数字、非下划线。等价于[^A-Za-z0-9_]
  • \s(space): 匹配所有空白符。等价于 [ \t\n\r\f\v](注意这里可以匹配空格)
  • \S: 匹配所有非空白符。利用[\s\S]可以匹配任一字符
  • \d(digital): 匹配所有数字。等价于[0-9]
  • \D: 匹配所有非数字。等价于[^0-9]

特殊字符

部分字符具有特殊的含义。

  • \ : 转义字符,要单独表示匹配特殊字符时,在前面加上转移字符

  • . : 匹配任意单个字符,除了换行符\n

  • [ : 标记一个中括号表达式的开始,一对中括号[]代表一个集合,每次匹配会选择一个字符匹配[]中的内容。可以利用 - 表示区间,在中括号之中特殊字符大部分都会失去特殊含义(除了转移字符\、放在中括号第一位的^

    [abcd] 匹配 a 或 b 或 c 或 d
    [.*\] 匹配 . 或 * 或 \
    [a-z] 匹配 a-z之间任意一个字符
    [a-zA-Z0-9] 匹配 任意一个英文字符或数字
    [][] 匹配 ] 或 [
    
  • (:标记一个子表达式的开始(也就是对于python中的分组group),利用()可以捕获匹配到的正则表达式中的内容,子表达式可以获取以后使用。分组group相关的具体内容会在下面细说。

  • {:标记限界符表达式的开始,利用{n,m}的形式可以指定重复形式,具体见下文

  • ^ : 在中括号表达式中,放在第一个表示该中括号表达式的补集,否则放在表达式的开头时,代表要匹配输入字符串的首部

    [^][] 匹配 除了 [ 和 ] 之外的任一字符
    [^0-9] 匹配任一非数字字符
    [a-z^] 匹配 任一小写英文字符 or ^
    ^abc 匹配 abcd 而不匹配 dabc
    
  • $ : 放在表达式的尾部时,表示匹配输入字符串的尾部

    ^ab*c$ 匹配以a开头、中间有0或多个b、以c结尾的字符串
    [abc]$ 匹配在尾部的a或b或c的单个字符
    
  • |:或运算符,表示需要匹配的元素要么在|的左侧,要么在|的右侧,使用|经常会与()结合,例如(aa|bb)会匹配aa或bb,此时由于子表达式的副作用会捕获匹配的aa或bb,通过使用(?:xx|yy)的方式可以取消捕获。

限定符(控制匹配重复)

利用限定符指示一个正则表达式中某一子表达式要出现几次。

  • * : 匹配前面的子表达式零次或多次

    (aca)* 匹配 aca or acaaca or acaacaaca or 空字符串
    a*cd 匹配 aacd or cd or aaaaacd 
    
  • + : 匹配前面的子表达式一次或多次

    (abc)+ 匹配 abc or abcabc
    
  • ?: 匹配前面的子表达式零次或一此

    (abc)? 匹配 abc 或 空字符常
    
  • {n} : 匹配前面的子字符串n次

    a{5} 匹配 aaaaa
    (pix){3} 匹配 pixpixpix
    
  • {n,} : 匹配前面的子字符串n次或更多次

    (pix){2,} 匹配 pixpix or pixpixpix or ...
    
  • {n,m} : 匹配前面的子字符串至少n次,至多m次

    a{2,4} 匹配 aa or aaa or aaaa
    a+ 等价于 a{1,}
    a* 等价于 a{0,}
    a? 等价于 a{0,1}
    

其中 +*? 都是贪婪的,它们会尽可能匹配多的内容,在其后面加上?可以实现非贪婪或最小匹配。例如a+?\w*?x??

实用正则匹配

  • 邮箱匹配:包括大小写字母、下划线、数字、点号、中划线
[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9_+]+
  • 匹配身份证号码:xxxxxxyyyyMMddxyzh
xxxxxx:六位地区 [1-9]\d{5}
yyyy:出生年份18xx-21xx年 (18|19|20|21)\d{2} 
MM:出生月份 ((0[1-9])|10|11|12)
dd:初始日期 (([0-2][1-9])|10|20|30|31)
xyz:三位顺序码 \d{3}
h:校验码 [0-9Xx]

[1-9]\d{5}(18|19|20|21)\d{2}((0[1-9])|10|11|12)(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]

Python中使用正则匹配

为了防止\在python的字符串中的转义语义影响到正则表达式的判断,对字符串使用r前缀(raw)保证传入个compile()的字符串是原始语义

常见使用方式如下

import re 
regular = r'[a-z]+' # 正则表达式
p = re.compile(regular) # 构造出的正则表达式对象

构造出正则表达式对象后,该对象有以下方法,其中pos和endpos用于限定匹配位置(这些方法在re中也有直接的实现,差别就是入参多填写一个要匹配的正则表达式,例如re.findall(pattern, string, flags=0)

方法 作用
match(string[, pos[, endpos]]) 从字符串的开头开始匹配
search(string[, pos[, endpos]]) 匹配字符串,返回第一个匹配结果
findall(string[, pos[, endpos]]) 匹配字符串,返回所有的匹配结果
finditer(string[, pos[, endpos]]) 匹配字符串,以iterator的方式返回匹配结果

match() 和 search() 如果没有匹配成功返回 None ,如果匹配成功返回一个匹配对象Match Object。Match Object具有以下方法:

方法 作用
group([group1, ...]) 返回正则匹配的字符串。如果只有一个参数,结果就是一个字符串,如果有多个参数,结果就是一个元组(每个参数对应一个项),如果没有参数,组1默认到0(整个匹配都被返回)
start() 返回匹配的开始位置
end() 返回匹配的结束位置
span() 返回包含匹配 (start, end) 位置的tuple
groups() 返回捕获的分组构成的tuple
例如:
import re 
regular = r'[a-z]+' # 正则表达式
p = re.compile(regular) # 构造出的正则表达式对象
m = p.match('abcdABCD')
print(m.group()) # abcd
print(m.start()) # 0
print(m.end()) # 4
print(m.span()) # (0,4)

其中对于group()会返回对应的捕捉分组,group()group(0)默认返回匹配到的正则表达式,而group(i)会返回第i个捕获分组,groups()会返回所有分组构造的tuple,如下所示:

p = re.compile(r'(a)(b)(c)d(e)')
m = p.match('abcde')
print(m.group()) # abcde
print(m.group(0)) # abcde
print(m.group(1)) # a 返回第1个分组a
print(m.group(4)) # e 返回第4个分组e

for g in m.groups():  # 返回catch到的分组的tuple,若没有设置分组捕获则为空
    print(g) # a b c e
p = re.compile(r'ack')
m1 = p.match('no ack no ack')
m2 = p.search('no ack no ack')
print(m1) # None match只会从字符串的开头开始尝试匹配
print(m2) # <re.Match object; span=(3, 6), match='ack'> search则会扫描整个字符串,尝试匹配成功1次后返回对应的Match对象,可以看到第二个ack没有匹配到

常用的使用方法如下:

p = re.compile( 'regular string' )
m = p.search( 'string goes here' )
if m: # 匹配成功
    print('Search found: ', m.group())
    for g in m.groups(): # 查看捕获的group
      print('catch group ',g)
else: # 没有匹配成功
    print('No match')

findall() & finditer()

findall()会匹配字符串并返回所有的匹配结果,若未设置group捕获则会返回匹配成功的字符串构成的list,若设置了group捕获则会返回group构成的tuple对应的list

re.findall(r'\bf[a-z]*', 'which foot or hand fell fastest')
# ['foot', 'fell', 'fastest'] 没有设置group捕获,返回匹配成功的字符串构成的list
re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10')
# [('width', '20'), ('height', '10')] 设置了group捕获,返回group构成的tuple对应的list

finditer()的功能和findall()是一样的,只是返回的结果是一个callable_iterator,迭代器中的内容为Match Object,使用finditer()可以使用到Match Object的函数(例如span()):

iter = re.finditer(r'(\w+)=(\d+)', 'set width=20 and height=10')
print(type(iter)) # <class 'callable_iterator'>
for m in iter: 
    print(type(m)) # <class 're.Match'>
    print(m.groups()) # ('width', '20')   ('height', '10')
    print(m.group())  # 'width=20'        'height=10'
    print(m.span())   # (4, 12)           (17, 26)

常见的使用方法如下:

iter = re.finditer('regul str','matched str')
if iter: # 匹配成功
  for m in iter: # 遍历callable_iterator
      print('match str is ',m.group())
      for g in m.groups(): # 遍历groups()
        print('catch group ',g)
else: #匹配失败
  print('no match')

分组

在复杂的正则中,很难跟踪组号。利用命名分组可以通过名称的方式引用groups。使用(?P<name>...)的方式可以获取到name对应的group,同时可以让Match Object使用 groupdict()方法获取一个对应的map

m = re.search(r'(?P<first>\w+) (?P<last>\w+)', 'Jane Doe') # 分组命名 first 和 last
print(m.groups())  # ('Jane', 'Doe')he
print(m.groupdict())  # {'first': 'Jane', 'last': 'Doe'}
print(m.group('last')) # 'Doe'

通过合理的使用分组命名可以快速提取需要的信息。

使用regex101可以在线检验自己的正则表达式。

posted @ 2021-03-19 14:59  海物chinono  阅读(156)  评论(0)    收藏  举报