python中的正则表达式re

正则表达式,又称规则表达式(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。

​ 许多程序设计语言都支持利用正则表达式进行字符串操作。例如,在Perl中就内建了一个功能强大的正则表达式引擎。正则表达式这个概念最初是由Unix中的工具软件(例如sed和grep)普及开的。正则表达式通常缩写成”regex”,单数有regexp、regex,复数有regexps、regexes、regexen。

​ 正则表达式是对字符串(包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为”元字符”))操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个”规则字符串”,这个”规则字符串”用来表达对字符串的一种过滤逻辑。正则表达式是一种文本模式,该模式描述在搜索文本时要匹配的一个或多个字符串。

正则表达式在很多语言里都支持,python也不例外,re模块就是正则表达式的应用.

1、正则表达式元字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
\	转义字符,将后边紧跟着的字符变成特殊字符,或将后边的特殊字符变成普通字符

如:在正则表达式中,"\n"换行符,"\\"则代表一个普通字符"\"

^ 匹配第一行行首,匹配多行需要传参 flags=re.MULTILINE
$ 匹配最后一行行尾,匹配多行需要传参 flags=re.MULTILINE
. 除"\r""\n"外,匹配任意的单个字符,要使"."匹配换行符,flags=re.DOTALL或re.S
| 或,如 "aaa|bbb|ccc",表示"aaa","bbb","ccc"三选一
? 匹配前边的子表达式0次或1次,等价于{0,1},非贪婪模式
+ 匹配前边的子表达式1次或多次,等价于{1,}
* 匹配前边的子表达式0次或多次,,等价于{0,}
{} {n}匹配前边的子表达式n次,{n,}匹配前边的子表达式至少n次,{n,m}匹配前边的子表达式n~m次
() 分组,从1开始,从左至右数"("为第几组,下标0为全部分组
[] 字符集匹配[]中的人一个字符,之匹配一次,如[abc]:表示"a","b","c"三选一。也可以给定范围(必须是连续的才行),如[a-z]:表示a到z任意一个字符。

还可以取反,如[^abc]:除"a","b","c"外的任意字符。注:[]中只有"^","-","\"三个特殊字符,其中"\"代表转义字符,其它的都代表原本普通的字符,如:[.]只是一个普通的点字符
注:要使用元字符本身的普通字符,请使用转义字符转义一下,如 :"\(" 在正则表达式中代表普通给"("字符,其它的雷同

分组的用法:

1
2
3
4
(\num)	引用指定第几分组的值
(?P<name>) 指定分组别名"name"
(?P=name) 引用指定别名的分组的值
(?:) 相当于去分组化,变成"与"()子集,如:"abc(?:qqq|www)"-->相当于"abcqqq|abcwww"

2、预定义字符集

1
2
3
4
5
6
7
8
\b	匹配一个单词边界,也就是指单词和空格间的位置,其它特殊字符也可以是单词的边界,如"#","$","&","*"等
\B 匹配非单词边界
\d 匹配一个数字字符。等价于[0-9]
\D 匹配一个非数字字符。等价于[^0-9]
\s 匹配任何不可见字符,包括空格、制表符、换页符等,等价于[ \f\n\r\t\v]
\S 匹配任何可见字符。等价于[^ \f\n\r\t\v]
\w 匹配包括下划线的任何单词字符。这里的"单词"字符使用Unicode字符集,类似但不等价于“[A-Za-z0-9_]”,还包含汉字等它国字符
\W 匹配任何非单词字符。这里的"单词"字符使用Unicode字符集,类似但不等价于“[^A-Za-z0-9_]”,还包含汉字等它国字符

3、re模块常用函数

re.A 或 re.ASCII 使用ASCII字符集进行匹配(不常用)
re.I 或 re.IGNORECASE 忽略大小写匹配
re.L 或 re.LOCALE 使用当前预定字符类 \w \W \b \B \s \S 取决于当前区域设定(不常用)
re.U 或 re.UNICODE 使用Unicode字符类 \w \W \b \B \s \S \d \D 取决于unicode定义的字符属性(不常用)
re.M 或 re.MULTILINE 多行匹配,使”^”,”$”可以在每一行中都进行匹配
re.S 或 re.DOTALL 使 “.” 可以匹配换行符”\r”,”\n”
re.X 或 re.VERBOSE 去掉正则表达式中的所有空格符(不常用)

1)re.compile(pattern, flags=0) :创建模式对象

编译正则表达式模式,返回一个对象的模式。(可以把那些常用的正则表达式编译成正则表达式对象,这样可以提高一点效率。)

pattern: 编译时用的表达式字符串,flags 编译标志位,用于修改正则表达式的匹配方式,如:是否区分大小写,多行匹配等,(常用的flags即re模块常用函数)

1
2
3
>>> rg = re.compile(r"\d+", flags=0)
>>> re.findall(rg, "a1bb22ccc333")
['1', '22', '333']

2)re.findall(pattern, string, flags=0)

re.findall遍历匹配,可以获取字符串中所有匹配的字符串,返回一个列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> re.findall(r"hello", "hello world hello")  # 普通匹配
['hello', 'hello']
>>> re.findall(r"^hello", "hello world hello") # 匹配开头
['hello']
>>> re.findall(r"^hello", "hello world\nhello", flags=re.MULTILINE) # 多行匹配开头
['hello', 'hello']
>>> re.findall(r"hello$", "hello world hello") # 匹配结尾
['hello']
>>> re.findall(r"\d+", "aaa111bbb222ccc333") # 匹配数字
['111', '222', '333']
>>> re.findall(r"\d{2}", "aaa111bbb222ccc333") # 匹配两位的数字
['11', '22', '33']
>>> re.findall(r"ab|cd", "ab000cd00") # 匹配"ab"或"cd"
['ab', 'cd']
>>> re.findall(r"\(", "ab(cd")) # 匹配"("
['(']
>>> re.findall(r"(abc)+", "abcabcabc") # 想要匹配多个"abc",使用分组时会优先把分组的内容返回
['abc']
>>> re.findall(r"(?:abc)+", "abcabcabc") # 想要匹配多个"abc",(?:)把分组去掉,变成一个普通的字符串
['abcabcabc']

3)re.search(pattern, string, flags=0)

在字符串中寻找模式,只要找到一个符合条件的就返回.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> re.search(r"hello", "hello world hello")
<_sre.SRE_Match object; span=(0, 5), match='hello'> # 可以看到只返回了第一个"hello"
>>> re.search(r"hello", "hello world hello").group()
'hello'
>>> re.search(r"(abc) \1", "abc abc").group() # \1 引用分组1的值
'abc abc'
>>> re.search(r"(?P<name>abc) (?P=name)", "abc abc").group() # (?P=name) 引用分组别名"name"的值
'abc abc'
>>> re.search(r"(?P<name>zhansan) (?P<age>23)", "zhansan 23").group()
'zhansan 23'
>>> re.search(r"(?P<name>zhansan) (?P<age>23)", "zhansan 23").group(0) # 分组0--即匹配的结果
'zhansan 23'
>>> re.search(r"(?P<name>zhansan) (?P<age>23)", "zhansan 23").group(1) # 分组1的值
'zhansan'
>>> re.search(r"(?P<name>zhansan) (?P<age>23)", "zhansan 23").group(2) # 分组2的值
'23'
>>> re.search(r"(?P<name>zhansan) (?P<age>23)", "zhansan 23").group("name") # 分组别名name的值
'zhansan'
>>> re.search(r"(?P<name>zhansan) (?P<age>23)", "zhansan 23").group("age") # 分组别名age的值
'23'

4)re.match(pattern, string, flags=0)

在字符串开始处匹配模式.

1
2
3
4
5
6
7
8
>>> re.match(r"abc", "abcooooo").group()
'abc'
>>>m = re.search('asd','ASDasd')
>>>m
<_sre.SRE_Match object at 0xb72cd6e8> #匹配到了,返回MatchObject(True)
>>>m = re.search('asd','ASDASD')
>>>m
None #没有匹配到,返回None(False)

5)re.finditer(pattern, string, flags=0)

finditer与findall相似,只不过finditer返回一个迭代器,通过group()可以获取值,搜索string,返回一个顺序访问每一个匹配结果(Match对象)的迭代器。找到 RE 匹配的所有子串,并把它们作为一个迭代器返回。

1
2
3
4
5
6
iter = re.finditer(r'\d+','12 drumm44ers drumming, 11 ... 10 ...')
for i in iter:
print('*'*20)
print(i)
print(i.group())
print(i.span())

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
********************
<re.Match object; span=(0, 2), match='12'>
12
(0, 2)
********************
<re.Match object; span=(8, 10), match='44'>
44
(8, 10)
********************
<re.Match object; span=(24, 26), match='11'>
11
(24, 26)
********************
<re.Match object; span=(31, 33), match='10'>
10
(31, 33)

6)re.sub(pattern, repl, string, count=0, flags=0)

sub按照给定的规则将string字符串中的相应的片段替换为repl,count 最多替换的次数,count=0默认为全部替换,返回替换后的字符串

1
2
>>> re.sub(r"\d+", "$", "aaa1bb2ccc333")  # 将连续的数字变成"$"
'aaa$bb$ccc$'

re.sub还允许使用函数对匹配项的替换进行复杂的处理。

如:re.sub(r’\s’, lambda m: ‘[‘ + m.group(0) + ‘]’, text, 0);将字符串中的空格’ ‘替换为’[ ]’。

1
2
3
4
5
import re
text = "JGood is a handsome boy, he is cool, clever, and so on..."
print(re.sub(r'\s+', lambda m:'['+m.group(0)+']', text,0))
执行结果如下:
JGood[ ]is[ ]a[ ]handsome[ ]boy,[ ]he[ ]is[ ]cool,[ ]clever,[ ]and[ ]so[ ]on...

7)re.subn(pattern, repl, string, count=0, flags=0)

返回替换次数,与sub相似,count 最多替换的次数,count=0默认为全部替换,返回 一个元组,下标0为替换后的字符串,下标1成功替换的次数

1
2
3
4
5
6
7
8
1 >>> re.subn(r"\d+", "$", "aaabbccc")
2 ('aaabbccc', 0) #未出现替换
3 >>> re.subn(r"\d+", "$", "aaa11bb22ccc")
4 ('aaa$bb$ccc', 2) #成功替换了两次
5 >>> re.subn(r"\d+", "$", "aaa11bb22ccc", 1) # 最多替换1次
6 ('aaa$bb22ccc', 1)
7 >>> re.subn(r"\d+", "$", "aaa11bb22ccc", 10) # 最多替换10次
8 ('aaa$bb$ccc', 2) # 成功替换2次

8)re.split(pattern, string, maxsplit=0, flags=0)

通过给定规则,将string进行切割,maxsplit最多切割次数,maxsplit=0默认全部切割,返回一个列表

1
2
3
4
5
6
1 >>> re.split(r"@", "a#b#c#d#e")  # 没有找到"@",不切割
2 ['a#b#c#d#e']
3 >>> re.split(r"#", "a#b#c#d#e") # 按"#"对字符串进行切割
4 ['a', 'b', 'c', 'd', 'e']
5 >>> re.split(r"#", "a#b#c#d#e", 2) # 按"#"对字符串进行切割,最多切割2次
6 ['a', 'b', 'c#d#e']

9)group:获取子模式(组)的匹配项

match和search一旦匹配成功,就是一个match object对象,需要用group()才可以打印匹配到的字符串。

b. group (n,m) 返回组号为n,m所匹配的字符串,如果组号不存在,则返回indexError异常
c.groups()方法返回一个包含正则表达式中所有小组字符串的元组,从 1 到所含的小组号,通常groups()不需要参数,返回一个元组,元组中的元就是正则表达式中定义的组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>>pat = re.compile(r'www\.(.*)\.(.*)')    #用()表示1个组,2个组
>>>m = pat.match('www.dxy.com')
>>>m
<re.Match object; span=(0, 11), match='www.dxy.com'>
>>>m.group() #默认为0,表示匹配整个字符串
'www.dxy.com'
>>>m.group(1) #返回给定组1匹配的子字符串
'dxy'
>>>m.group(2)
'com'
>>>m.groups()
('dxy', 'com')
>>>m.start(2) #组2开始的索引
8
>>>m.end(2) #组2结束的索引
11
>>>m.span(2) #组2开始、结束的索引
(8, 11)

10)贪婪匹配与非贪婪匹配

贪婪匹配,也就是尽可能多的匹配,后面加?号使其变成惰性匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a = re.findall(r"a(\d+?)",'a23b')
print(a)
b = re.findall(r"a(\d+)",'a23b')
print(b)
执行结果:
['2']
['23']
a = re.match('<(.*)>','<H1>title<H1>').group()
print(a)
b = re.match('<(.*?)>','<H1>title<H1>').group()
print(b)
执行结果:
<H1>title<H1>
<H1>

最后:用flags时遇到的小坑

1
2
3
4
print(re.split('a','1A1a2A3',re.I))#输出结果并未能区分大小写
这是因为re.split(pattern,string,maxsplit,flags)默认是四个参数,
当我们传入的三个参数的时候,系统会默认re.I是第三个参数,所以就没起作用。
如果想让这里的re.I起作用,写成flags=re.I即可。
hey!baby,站住,点它!