文章目录
- 一、正则表达式简介
- 二、正则表达式的语法规则
- 三、Python Re模块
- 3.1 简介
- 3.2 标志 flags
- 3.3 re.compile(*pattern, flags=0*)
- 3.4 re.match(*pattern, string[, flags]*)
- 3.5 re.search(*pattern, string[, flags]*)
- 3.6 re.split(*pattern, string[, maxsplit]*)
- 3.7 re.findall(*pattern, string[, flags]*)
- 3.8 re.finditer(*pattern, string[, flags]*)
- 3.9 re.sub(*pattern, repl, string[, count]*)
- 3.10 re.subn(*pattern, repl, string[, count]*)
一、正则表达式简介
正则表达式是对字符串进行模糊匹配,提取自己需要的字符串部分,对于所有编程语言通用。对于Python来说我们使用re模块来操作正则表达式来匹配我们想要的结果。查看此篇文章,建议先看目录。
体系结构简述如下:
二、正则表达式的语法规则
2.1 字符
语法 | 说明 | 表达式实例 | 完整匹配的字符串 |
---|---|---|---|
一般字符 | 匹配自身 | abc | abc |
. | 匹配任意除换行符“\n”外的字符。 在DOTALL模式中也能匹配换行符。 | a.c | abc |
\ | 转义字符,使后面一个字符改变原来的意思。 如果字符串中有字符 * 匹配,可以使用\* 或者字符集[*] 。 | a\.c a\\c | a.c a\c |
[...] | 字符集(字符类)。对应的位置可以是字符集中任意字符。字符集中的字符可以逐个列出,也可以给出范围,如[abc] 或[a-c] 。第一个字符如果是^ 则表示取反,如[^abc] 表示不是abc 的其他字符。所有的特殊字符在字符集中都失去其原有的特殊含义。在字符集中如果要使用 ] 、- 或^ ,可以在前面加上反斜杠,或者把] 、- 放在第一个字符,把^ 放在非第一个字符。 | a[bcd]e | abe ace ade |
2.2 预定义字符集(可以写在字符集[…]中)
语法 | 说明 | 表达式实例 | 完整匹配的字符串 |
---|---|---|---|
\d | 数字:[0-9] | a\dc | a1c |
\D | 非数字:[^\d] | a\Dc | abc |
\s | 空白字符:[<空格>\t\r\n\f\v] | a\sc | a c |
\S | 非空白字符:[^\s] | a\Sc | abc |
\w | 单词字符:[A-Za-z0-9_] | a\wc | abc |
\W | 非单词字符:[^\W] | a\Wc | a c |
2.3 数量词(用在字符或(…)之后)
语法 | 说明 | 表达式实例 | 完整匹配的字符串 |
---|---|---|---|
* | 匹配前一个字符0或无限次 | abc* | ab abccc |
+ | 匹配前一个字符1次或无限次 | abc+ | abc abccc |
? | 匹配前一个字符0次或1次 | abc? | ab abc |
{m} | 匹配前一个字符m次 | ab{2}c | abbc |
{m,n} | 匹配前一个字符m至n次。 m和n可以省略: 1、若省略m,则匹配0至n次。 2、若省略n,则匹配m至无限次。 | ab{1,2}c | abc abbc |
*? +? ?? {m,n}? | 使得* + {m,n} 变成非贪婪模式。 | 示例将在下文中介绍 |
2.4 边界匹配(不消耗待匹配字符串中的字符)
语法 | 说明 | 表达式实例 | 完整匹配的字符串 |
---|---|---|---|
^ | 匹配字符串开头。 在多行模式中匹配每一行的开头。 | ^abc | abc |
$ | 匹配字符串末尾。 在多行模式中匹配每一行的末尾。 | abc$ | abc |
\A | 仅匹配字符串开头。与^ 效果一样,且仅Python独有。其他语言无此语法。 | \Aabc | abc |
\Z | 仅匹配字符串末尾。与$ 效果一样,且仅Python独有。其他语言无此语法。 | abc\Z | abc |
\b | 仅匹配\w 和\W 之间。匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配"never" 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。 | a\b!bc | a!bc |
\B | [^\b]匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。 | a\Bbc | abc |
2.5 逻辑、分组
语法 | 说明 | 表达式实例 | 完整匹配的字符串 |
---|---|---|---|
| | | 代表左后表达式任意匹配一个。他总是先尝试匹配左边的表达式,一旦成功匹配则跳过匹配右边的表达式。 如果 | 没有被包括在() 中,则它的范围是整个正则表达式。 | abc|def | abc def |
(...) | 被括起来的表达式将作为分组,从表达式左边开始没遇到一个分组的左括号( ,编号+1。另外,分组表达式作为一个整体,以后可以接数量词。表达式中的` | `仅在该组中有效。 | (abc){2} a(123|456)c |
(?P<name>...) | 分组,除了原有的编号外再指定一个额外的别名。 | (?P<id>abc){2} | abcabc |
\<number> | 引用编号为<number>的分组匹配到的字符串。 | (\d)abc\1 | 1abc1 5abc5 注:相当于(\d)匹配的字符再用了一次。 |
(?P=name) | 引用别名为<name>的分组匹配到的字符。 | (?P<id>\d)abc(?P=id) | 1abc1 5abc5 |
2.6 特殊构造(不作为分组)
语法 | 说明 | 表达式实例 | 完整匹配的字符串 |
---|---|---|---|
(?:...) | (...) 的不分组版本,用于使用| 或后接数量词。 | (?:abc){2} | abcabc |
(?iLmsux) | iLmsux的每一个字符代表一个匹配模式,只能用在正则表达式的开头,可选多个。匹配模式将在下文中介绍。 | (?i)abc | AbC |
(?#...) | #后的内容将作为注释被忽略。![]() | abc(?#comment)123 | abc123 |
(?=...) | 之后的字符串内容需要匹配表达式才能成功匹配。 不消耗字符串内容[1]。 | a(?=\d) | 后面是数字的a |
(?!...) | 之后的字符串内容需要不匹配表达式才能成功匹配。 不消耗字符串内容[1]。 | a(?!\d) | 后面不是数字的a |
(?<=...) | 之前的字符串内容需要匹配表达式才能成功匹配。 不消耗字符串内容[1]。 | (?<=\d)a | 前面是数字a |
(?<!...) | 之前的字符串内容需要不匹配表达式才能成功匹配。 不消耗字符串内容[1]。 | (?<!\d)a | 前面不是数字的a |
(?(id/name)yes-pattern|no-pattern | 如果编号为id/别名为name的组匹配到字符,则需要匹配yes-patttern,否则需要匹配no-pattern。|no-pattern可以省略。 | (\d)abc(?(1)\d|abc) | 1abc2 abcabc |
2.7 正则表达式相关说明
(1) 数量词的贪婪模式与非贪婪模式
正则表达式通常用于在文本中查找匹配的字符串。Python里的数量词默认是贪婪的(在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符;非贪婪的则
相反,总是尝试匹配尽可能少的字符。例如:正则表达式"ab*“如果用于查找"abbbc”,将找到"abbb"。而如果使用非贪婪的数量词"ab*?“,将找到"a”。
*?
重复任意次,但尽可能少重复
+?
重复1次或更多次,但尽可能少重复
??
重复0次或1次,但尽可能少重复
{n,m}?
重复n到m次,但尽可能少重复
{n,}?
重复n次以上,但尽可能少重复
注:*?
如果在表达式的末尾,那么就无法匹配到字符,也就是匹配前一个字符0次,但是如果是在表达式中,为了匹配*?
之后的字符,我们就有可能匹配到多个。
.*?
的用法
.
是任意字符
*
是匹配前一个字符0次或无限次
?
是非贪婪模式。
合在一起就是取尽量少的任意字符,一般不会这么单独写,他大多数用在:
.*?x
就是取前面任意长度的字符,直到一个x出现
(2)不消耗字符串内容
正则表达式是对给定的字符串进行匹配。也就是说,一般匹配了一个字符后,该字符就被消耗,就不能被Regular Expression的其他部分匹配了。
但是预查不是,因为他不消耗字符。示例如下代码:
import re
re.search(r'abc', 'abcdefg')
<re.Match object; span=(0, 3), match='abc'>
re.search(r'a(b)bc', 'abcdefg') # 未匹配出结果,因为字符串中b被(b)消耗掉了,没有另外的b符合括号外的b
re.search(r'a(b)bc', 'abbcdefg') # 匹配出的结果,字符串中有两个b可供消耗
<re.Match object; span=(0, 4), match='abbc'>
re.search(r'a(?=b)bc', 'abcdefg') # 匹配成功,因为第一个(?=b)中的b不消耗字符串内容,故还留有一个b被括号外的b消耗
<re.Match object; span=(0, 3), match='abc'>
(3)转义字符
与大多数编程语言相同,正则表达式里使用”\”作为转义字符,这就可能造成反斜杠困扰。假如你需要匹配文本中的字符”\”,那么使用编程语言表示的正则表达式里将需要4个反斜杠”\\”:前两个和后两个分别用于在编程语言里转义成反斜杠,转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。
Python里的原生字符串很好地解决了这个问题,这个例子中的正则表达式可以使用r”\”表示。同样,匹配一个数字的”\d”可以写成r”\d”。
三、Python Re模块
3.1 简介
Python 自带了re模块,它提供了对正则表达式的支持。
上文中我们介绍了很多正则表达式的语法,我们需要上面的语法写出一个表达式,符合这个表达式规则的文本,就是我们想要的内容。而这个表达式我们也称为pattern。
3.2 标志 flags
简写 | 全称 | 用法 |
---|---|---|
re.A | re.ASCII | 让 \w, \W, \b, \B, \d, \D, \s 和 \S 只匹配ASCII,而不是Unicode。不是很理解,暂时还没有看到能用到的地方。 |
re.M | re.MULTILINE | 多行模式,改变^ 和$ 的行为。使其可以匹配多行(也就是\n为一行结束)的开头和结尾。在 ‘foo1\nfoo2\n’ 中搜索 foo.$ ,通常匹配 ‘foo2’,但在 re.MULTILINE模式下可以匹配到 ‘foo1’;在 ‘foo\n’ 中搜索 $ 会找到两个(空的)匹配:一个在换行符之前,一个在字符串的末尾。 |
re.S | re.DOTALL | 点任意匹配模式,改变. 的行为。让 . 特殊字符匹配任何字符,包括换行符;如果没有这个标记,. 就匹配 除了 换行符的其他任意字符。 |
re.I | re.IGNORECASE | 忽略大小写 |
·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 | 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释。 |
3.3 re.compile(pattern, flags=0)
Pattern就是我们需要写的那个正则表达式,我们需要通过这个表达式去匹配到我们需要的字符串从而进行判断、提取以及替换等操作。
在Python中compile(pattern, flags=0)
方法可以让我们获得一个pattern对象。其中pattern是我们需要填写的表达式,flags就是我们上一节提到的标志。书写和使用方法主要是一下三种:
- 如果需要多次使用这个正则表达式的话,使用 re.compile() 和保存这个正则对象以便复用,可以让程序更加高效。
prog = re.compile(pattern)
result = prog.match(string)
- 也可以用如下书写方式,不过不是很推荐。
prog = re.compile(pattern)
result = re.match(prog)
- 如果只使用一次,那我们直接写成如下方式比较方便。
result = re.match(pattern, string)
3.4 re.match(pattern, string[, flags])
如果 string 开头的零个或多个字符与正则表达式 pattern 匹配,则返回相应的 Match。 如果字符串与模式不匹配则返回 None;请注意这与零长度匹配是不同的。
注意即便是 MULTILINE
多行模式, re.match()
也只匹配字符串的开始位置,而不匹配每行开始。
如果你想定位 string 的任何位置,使用 search()
来替代。
import re
pattern = re.compile(r'abc')
result1 = pattern.match('abcdefabc') # result1: <re.Match object; span=(0, 3), match='abc'>
result2 = pattern.match('defabc') # result2: None
有两点需要注意:
re.match()
只从字符串开头匹配,从result1和result2的匹配结果可以看出,result2中字符串末尾的’abc’并没有被匹配到。- 匹配对象总是有一个布尔值 True。如果没有匹配的话 match() 和 search() 返回 None 所以你可以简单的用 if 语句来判断是否匹配。
# 接上一个代码块中的结果
if result1:
print(True)
if not result2:
print(False)
3.4.1 match的属性
- string: 匹配时使用的文本
- re: 匹配时使用的Pattern对象
- pos: 文本中正则表达式开始搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名参数相同
- endpos: 文本中正则表达式结束搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名参数相同。
- lastindex: 最后一个被捕获的分组在文本中的索引。如果没有被捕获的分组,将为None。
- lastgroup: 最后一个被捕获的分组的别名。如果这个分组没有别名或者没有被捕获的分组,将为None。
3.4.1.1 string
匹配时使用的文本,也就是re.match(pattern, string, flags=0)
中的string
。
import re
pattern = re.compile(r'(abc\d+)(de)f')
result1 = pattern.match('abc123defabc')
result2 = pattern.match('abc12fabc')
string_value1 = result1.string # string_value1: 'abc123defabc'
string_value2 = result2.string # 会报错,因为result2没有匹配到值,结果为None,所以没有string属性
3.4.1.2 re
匹配时使用的Pattern对象。也就是re.match(pattern, string, flags=0)
中的pattern
。
import re
pattern = re.compile(r'(abc\d+)(de)f')
result1 = pattern.match('abc123defabc')
result2 = pattern.match('abc12fabc')
re_value1 = result1.re # re_value1: re.compile('(abc\\d+)(de)f')
3.4.1.2 pos
文本中正则表达式开始搜索的索引。值与Pattern.match()
和Pattern.seach()
方法的同名参数相同。好像只要match()
正则匹配成功,这个属性值就必然为0,暂时没遇到例外的情况
import re
pattern = re.compile(r'(abc\d+)(de)f')
result1 = pattern.match('abc123defabc')
result2 = pattern.match('abc12fabc')
pos_value = result1.pos # pos_value: 0
3.4.1.3 endpos
文本中正则表达式结束搜索的索引。值与Pattern.match()
和Pattern.seach()
方法的同名参数相同。
import re
pattern = re.compile(r'(abc\d+)(de)f')
result1 = pattern.match('abc123defabc') # 字符长度为12
result2 = pattern.match('abc12fabc')
endpos_value = result1.endpos # endpos_value: 12
3.4.1.4 lastindex
最后一个被捕获的分组在文本中的索引。如果没有被捕获的分组,将为None。这里提到了一个分组的概念,这就是我们前面介绍的(...)
会捕获分组并编号或命名,编号为1, 2。在下面代码中有两个(...)
所以捕获两个分组。lastindex标识最后一个被捕获分组的索引,也就是(de)
索引(也就是上面的编号)为2。
import re
pattern = re.compile(r'(abc\d+)(de)f')
result1 = pattern.match('abc123defabc')
result2 = pattern.match('abc12fabc')
lastindex = result1.lastindex # lastindex: 2
3.4.1.5 lastgroup
最后一个被捕获的分组的别名。如果这个分组没有别名或者没有被捕获的分组,将为None。
import re
pattern1 = re.compile(r'(abc\d+)(de)f')
result1 = pattern1.match('abc123defabc')
lastgroup1 = result1.lastgroup # lastgroup1: None # 因为没有分组命名,故为None
pattern2 = re.compile(r'(abc\d+)(?P<name>de)f')
result2 = pattern2.match('abc123defabc')
lastgroup2 = result2.lastgroup # lastgroup2: 'name' # 正则表达式中给分组命名为了name
3.4.2 match的方法
- group([group1, …]):获得一个或多个分组截获的字符串
- groups([default]):以元组形式返回全部分组截获的字符串。
- groupdict([default]):返回以有别名的组的别名为键、以该组截获的子串为值的字典,没有别名的组不包含在内。
- start([group]):返回指定的组截获的子串在string中的起始索引(子串第一个字符的索引)。
- end([group]):返回指定的组截获的子串在string中的结束索引(子串最后一个字符的索引+1)。
- span([group]):返回(start(group), end(group))。
- expand(template):将匹配到的分组代入template中然后返回。
3.4.2.1 group([group1, …])
获得一个或多个分组截获的字符串;指定多个参数时将以元组形式返回。group1可以使用编号也可以使用别名;编号0代表整个匹配的子串;不填写参数时,返回group(0);没有截获字符串的组返回None;截获了多次的组返回最后一次截获的子串。
import re
pattern1 = re.compile(r'(abc\d+)(de)f')
result1 = pattern1.match('abc123defabc')
group0 = result1.group() # group0: 'abc123def'
group1 = result1.group(1) # group1: 'abc123'
group2 = result1.group(2) # group2: 'de'
3.4.2.2 groups([default])
以元组形式返回全部分组截获的字符串。相当于调用group(1,2,…last)。default表示没有截获字符串的组以这个值替代,默认为None。
import re
pattern1 = re.compile(r'(abc\d+)(de)f')
result1 = pattern1.match('abc123defabc')
groups = result1.groups() # groups: ('abc123', 'de')
3.4.2.3 groupdict([default])
返回以有别名的组的别名为键、以该组截获的子串为值的字典,没有别名的组不包含在内。default含义同上。
import re
pattern1 = re.compile(r'(abc\d+)(de)f')
result1 = pattern1.match('abc123defabc')
groupdic1 = result1.groupdict() # groupdic1: {} # 这里由于没对分组命名,所以取不到键值对
pattern2 = re.compile(r'(?P<name>abc\d+)(de)f')
result2 = pattern2.match('abc123defabc')
groupdic2 = result2.groupdict() # groupdic2: {'name': 'abc123'} # 这里取到有分组命令的键值对
3.4.2.4 start([group])
返回指定的组截获的子串在string中的起始索引(子串第一个字符的索引)。group默认值为0。
import re
pattern1 = re.compile(r'(abc\d+)(de)f')
result1 = pattern1.match('abc123defabc')
start0 = result1.start() # start0: 0
start2 = result1.start(2) # start2: 6 #第二个分组的开始位置,也就是字母d的开始位置
3.4.2.5 end([group])
返回指定的组截获的子串在string中的结束索引(子串最后一个字符的索引+1)。group默认值为0。
import re
pattern1 = re.compile(r'(abc\d+)(de)f')
result1 = pattern1.match('abc123defabc')
end0 = result1.end() # end0: 9
end2 = result1.end(2) # end2: 8
3.4.2.6 span([group])
返回(start(group), end(group))。
import re
pattern1 = re.compile(r'(abc\d+)(de)f')
result1 = pattern1.match('abc123defabc')
span0 = result1.span() # span0: (0, 9)
span2 = result1.span(2) # span2: (6, 8)
3.4.2.7 expand(template)
将匹配到的分组代入template中然后返回。template中可以使用\id或\g、\g引用分组,但不能使用编号0。\id与\g是等价的;但\10将被认为是第10个分组,如果你想表达\1之后是字符’0’,只能使用\g0。暂时没找到相应的使用示例。
3.5 re.search(pattern, string[, flags])
search方法与match方法极其类似,区别在于match()函数只检测re是不是在string的开始位置匹配,search()会扫描整个string查找匹配,match()只有在0位置匹配成功的话才有返回,如果不是开始位置匹配成功的话,match()就返回None。同样,search方法的返回对象同样match()返回对象的方法和属性。
import re
pattern1 = re.compile(r'abc')
result1 = pattern1.search('qqqabc123defabc') # result1: <re.Match object; span=(3, 6), match='abc'> #只匹配从左到右第一个能匹配到的字符,第二个abc无法匹配和获取
result2 = pattern1.search('qqq')
有两点需要注意:
re.search()
不需要像re.match()
只从字符串开头匹配。但是从上述代码中看到,只能匹配一次。- 匹配对象总是有一个布尔值 True。如果没有匹配的话 match() 和 search() 返回 None 所以你可以简单的用 if 语句来判断是否匹配。
# 接上一个代码块中的结果
if result1:
print(True)
if not result2:
print(False)
3.6 re.split(pattern, string[, maxsplit])
按照能够匹配的子串将string分割后返回列表。maxsplit用于指定最大分割次数,不指定将全部分割。
import re
pattern1 = re.compile(r'\d+')
result1 = pattern1.split('one1two2three3four4') # result1: ['one', 'two', 'three', 'four', ''] # 匹配到的字符不在分割后的内容里
3.7 re.findall(pattern, string[, flags])
搜索string,以列表形式返回全部能匹配的子串。
import re
pattern1 = re.compile(r'\d+')
result1 = pattern1.findall('one1two2three3four4') # result1: ['1', '2', '3', '4'] # 这返回的是一个列表可以使用列表的操作
返回结果取决于模式中捕获组的数量。如果没有组,返回与整个模式匹配的字符串列表。如果有且仅有一个组,返回与该组匹配的字符串列表。如果有多个组,返回与这些组匹配的字符串元组列表。非捕获组不影响结果。
import re
pattern1 = re.compile(r'e\d+')
result1 = pattern1.findall('one1two2three3four4') # result1: ['e1', 'e3'] # 没有组的时候返回匹配到的字符串的列表
pattern2 = re.compile(r'e(\d+)')
result2 = pattern2.findall('one1two2three3four4') # result1: ['1', '3'] # 有组的时候返回的是匹配到字符串里面分组的列表
pattern3 = re.compile(r'e(\d+).*?(\d+)')
result3 = pattern3.findall('one1two2three3four4') # result3: [('1', '2'), ('3', '4')] # 非贪婪模式
pattern4 = re.compile(r'e(\d+).*(\d+)')
result4 = pattern4.findall('one1two2three3four4') # result4: [('1', '4')] # 贪婪模式
3.8 re.finditer(pattern, string[, flags])
搜索string,返回一个顺序访问每一个匹配结果(Match对象)的迭代器。
import re
pattern1 = re.compile(r'\d+')
result1 = pattern1.finditer('one1two2three3four4') # result1: <callable_iterator object at 0x000002C689E0ADF0>
for i in result1:
print(i) # 输出1 2 3 4
3.9 re.sub(pattern, repl, string[, count])
使用repl替换string中每一个匹配的子串后返回替换后的字符串。
当repl是一个字符串时,可以使用\id或\g、\g引用分组,但不能使用编号0。
当repl是一个方法时,这个方法应当只接受一个参数(Match对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。
count用于指定最多替换次数,不指定时全部替换。
更高级用法参见 re.sub
import re
pattern1 = re.compile(r'([a-zA-Z]+)(\d+)')
findall1 = pattern1.findall('one1two2three3four4') # findall1: [('one', '1'), ('two', '2'), ('three', '3'), ('four', '4')]
sub1 = pattern1.sub(r'\2\1', 'one1two2three3four4') # sub1: '1one2two3three4four'
3.10 re.subn(pattern, repl, string[, count])
返回 (sub(repl, string[, count]), 替换次数)。也就是返货sub的替换结果和替换次数,形式为元组
import re
pattern1 = re.compile(r'([a-zA-Z]+)(\d+)')
findall1 = pattern1.findall('one1two2three3four4') # findall1: [('one', '1'), ('two', '2'), ('three', '3'), ('four', '4')]
subn1 = pattern1.subn(r'\2\1', 'one1two2three3four4') #subn1: ('1one2two3three4four', 4)