正则表达式是一种小型、高度专业化的编程语言。适用于任何语言,在 Python 中通过 re 模块实现。正则模式被编译成一系列的字节码,然后由 C 语言编写的匹配引擎执行。给字符串模糊匹配
正则用于匹配字符串,匹配字符串可以完全匹配和模糊匹配:
- 完全匹配:普通字符,大多数字符和字母都和自身匹配
re.findall('rose', 'lialrosetom')
- 模糊匹配:元字符,不需要完全匹配即能匹配成功
. ^ $ * + ? { } [ ] | ( )
元字符
正则可以使用普通字符进行完全匹配,也可以使用元字符进行模糊匹配。正则表达式中所有元字符: . ^ $ * + ? { } [ ] | ( )
。
通配符
正则中用一个点(.)来表示通配符,它可以匹配除换行符( )以外任何字符。一个点只能匹配一个字符
>>> re.findall('s..x', 'abshuxijd')
['shux']
开头结尾
正则中尖角号(^)用于匹配一行字符串的开头,($)用于匹配一行字符串的结尾。必须以匹配对象开头或结尾才能匹配成功,否则匹配失败。
>>> re.findall(r'^hello,d+','hello,123')
['hello,123']
>>> re.findall(r'^hello,d+','hi hello,123')
[]
>>> re.findall(r'hello,d+$','hello,123')
['hello,123']
>>> re.findall(r'hello,d+$','hello,123s')
[]
重复匹配
不管是点(.),还是数字匹配(d)都只能匹配一个字符或数字。要想匹配多个就要进行重复匹配,正则中能够重复匹配的元字符有:* + ? {}
。
元字符 | 描述 | 元字符 | 描述 |
---|---|---|---|
* | 对星号前面的子表达式匹配重复 [0, ∞] 次 | + | 对加号前面的子表达式匹配重复 [1, ∞] 次 |
? | 对问号前面的子表达式匹配重复 [0,1] 次,非贪婪模式 | {n} | 精确匹配 n 个前子表达式 |
**星号 ***
星号(*)可以对它前面的子表达式重复 0 次 或多次。
# 对 abc 的 c 重复 0 到多次,即可匹配 ab、abc、abcc、abc...
>>> re.findall(r'abc*','ab456abc123abccc')
['ab', 'abc', 'abccc']
加号 +
加号(+)可以对它前面的子表达式重复 1 次或多次。
>>> re.findall(r'abc+','ab456abc123abccc') # 即至少有有一个 c
['abc', 'abccc']
问号 ?
问号(?)可以对它前面的子表达式重复 0 次或 1 次。
>>> re.findall(r'abc?','ab456abc123abccc')
['ab', 'abc', 'abc']
大括号 {}
大括号({})可以对它前面的子表达式精确重复几次,它有多重模式:
{n}
:对它前面的子表达式至少重复 n 次{m, n}
:对它前面的子表达式重复 m 到 n 次{0, ∞}
:相当于星号(*){1, ∞}
:相当于加号(+){0, 1}
:相当于问号(?)
>>> re.findall(r'abc{3}','ab456abc123abccc') # c 重复 3 次,匹配到 abccc
['abccc']
>>> re.findall(r'abc{1,3}','ab456abc123abccc') # 匹配 abc、abccc
['abc', 'abccc']
>>> re.findall(r'abc{1,3}','abc456abcc123abccc')
['abc', 'abcc', 'abccc']
管道符
管道符(|)表示或,a|b
表示 a 或 b。
>>> re.findall(r'a|b', 'abc')
['a', 'b']
>>> re.findall(r'ka|b', 'kahbc')
['ka', 'b']
>>> re.findall(r'ka|b', 'kahc')
['ka']
>>> re.findall(r'ka|b', 'ka|bhc')
['ka', 'b']
字符集
在正则表达式中,中括号([ ] )表示一个字符集,它用来表示一组字符。字符集中除 - ^
有特殊意义外,其他元字符都是普通字符。
[mm]
:匹配 m 或 n[^mn]
:匹配除 m、n 以外的所有字符
>>> re.findall(r'abc[de]','abcd123abce567')
['abcd', 'abce']
>>> re.findall(r'[^de]+','abcd123abce567')
['abc', '123abc', '567']
横杠 -
字符集中横杠(-)用来表示范围,[a-z]
表示 a - z 任意一个字母。
>>> re.findall(r'b[a-z]', 'bs')
['bs']
>>> re.findall(r'[1-9]','12ab45')
['1', '2', '4', '5']
>>> re.findall(r'[1-9]+','12ab45')
['12', '45']
>>> re.findall(r'[A-Za-z0-9]+','12abCD45')
['12abCD45']
>>> re.findall(r'd+@[A-Za-z0-9]+.[a-z]+','982561639@qq.com')
['982561639@qq.com']
尖角号 ^
尖角号(^)表示非,[^d]
除数字以外。
>>> re.findall(r'[^d]+','abc123def')
['abc', 'def']
>>> re.findall('b[^a-z]*', 'bs213') # s 没有匹配上,匹配停止
['b']
转义字符
反斜杠()表示对某个字符转义,可以把普通字符变成特殊字符,如:d
>>> re.findall(r'[d]+','abc123def456')
['123', '456']
转义字符
转义字符在元字符中是一个比较特殊的存在,在字符集里面、外面都有特殊意义。
- 反斜杠后面跟元字符去除特殊功能,如:
.
,其中这个点变成了普通的点 - 反斜杠后面跟普通字符实现特殊功能,如:
d
,匹配任何十进制数
转义字符 + 普通字符 = 特殊字符
d | 匹配任意十进制数,相当于 [0-9] |
---|---|
D | 匹配任何非数字字符,相当于 [^0-9] |
s | 匹配任意空白字符,相当于 [
fv] |
S | 匹配任意非空白字符,相当于 [^
fv] |
w | 匹配任意字母数字字符,相当于 [a-zA-Z0-9_] |
W | 匹配任意非字母数字字符,相当于 [^a-zA-Z0-9_] |
匹配任意一个特殊字符边界,如:空格、&、# 等 |
在 Python 中本身是有特殊意义的,在匹配时,需要给它转义,或者加上原生字符串 r :
>>> re.findall(r'abc','abc123') # 匹配的是特殊字符边界,abc 后面没有空格,匹配失败
[]
>>> re.findall('abc\b','abc 123') # abc 后面有空格,匹配成功
['abc']
>>> re.findall(r'abc','abc 123')
['abc']
>>> re.findall(r'abc','abc#123')
['abc']
给其他字符加上转义字符时:
import re
>>> re.findall('cl','abcle')
[]
>>> re.findall('c\l','abcle')
[]
>>> re.findall('c\\l','abcle')
['c\l']
>>> re.findall(r'c\l','abcle')
['c\l']
w 和 s
用法示例:
>>> re.findall(r'w', '12shdk34_') # 匹配数字字母以及下划线,加上+,匹配多个
['1', '2', 's', 'h', 'd', 'k', '3', '4', '_']
>>> re.findall(r'w+', '12shdk34_')
['12shdk34_']
>>> re.findall(r's+', '12 shdk 34_') # 匹配空白字符
[' ', ' ']
Tips:在使用正则的时候,尽量带上原生字符串 r,可以避免不必要的麻烦。
转义字符 + 元字符 = 普通字符
给元字符加上转义字符,元字符就失去了原有的意义,变成了一个普通字符。
>>> re.findall(r'www*', 'www*')
['www']
>>> re.findall(r'www*', 'www*') # 要想把 * 也匹配上,就要给 * 转义
['www*']
贪婪与非贪婪
关于重复匹配,正则表达式默认是按照贪婪匹配的方式去匹配,即在符合条件的情况下,尽可能多的去匹配。而非贪婪匹配恰好与之相反。
贪婪匹配:
>>> re.findall(r'abc*','abc123abcc') # 尽可能多的匹配(* 的范围是 0 或 多次,它取了 多 次)
['abc', 'abcc']
>>> re.findall(r'<.+>','<html><title>Hello World</title></html>')
['<html><title>Hello World</title></html>']
启用非贪婪模式,只需在表示重复的元字符后面加上一个问号(?)即可:
>>> re.findall(r'abc*?','abc123abcc') # 尽可能少的匹配(* 的范围是 0 或 多次,它取了 0 次)
['ab', 'ab']
>>> re.findall(r'<.+?>','<html><title>Hello World</title></html>')
['<html>', '<title>', '</title>', '</html>']
在贪婪匹配下,(.*)会尽可能多的匹配字符,因此它把 123456 匹配了,只留下了一个 7 给 d+
,最后得到的内容是 7 :
>>> content = 'Hello 1234567 World_This a Regex Demo'
>>> pattern = re.compile(r'^He.*(d+).*Demo$')
>>> re.findall(pattern,content)
['7']
有时贪婪匹配匹配的结果并不是我们想要的那样,给我们带来了很大不便,这时我们就要使用非贪婪匹配比较合适:
>>> pattern = re.compile(r'^He.*?(d+).*Demo$')
>>> re.findall(pattern,content)
['1234567']
修饰符
正则可以包含一些可选标志修饰符来控制匹配模式,修饰符被指定为一个可选的标志。
修饰符 | 描述 | 修饰符 | 描述 |
---|---|---|---|
re.I | 使匹配对大小不敏感 | re.L | 使本地化识别(locale-aware)匹配 |
re.M | 多行匹配,影响 ^ 和 $ | re.S | 使 . 匹配包含换行在内的所有字符(即点(.)换行符也能匹配) |
re.U | 根据 Unicode 字符集解析字符。影响 w W 和 B |
reX | 通过给与你更灵活的格式以便你将正则表达式写的更易理解 |
我们知道点(.)可以匹配除换行符以外的所有字符,但是当遇到换行时,就会引发匹配失败:
import re
content = """Hello 1234567 World_This
is a Regex Demo"""
result = re.match(r'^He.*?(d+).*?Demo$',content)
print(result.group(1))
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-2-bd9088917b1a> in <module>()
3 is a Regex Demo"""
4 result = re.match(r'^He.*?(d+).*?Demo$',content)
----> 5 print(result.group(1))
AttributeError: 'NoneType' object has no attribute 'group'
之所以会报没有 group
方法,是因为我们使用了点(.)匹配任意字符,但是不能匹配换行符,也就匹配失败。而我们又调用了 group
方法,针对这种情况,我们只需在 match 方法最后添加一个 re.S
的修饰符即可,以便使点(.)能匹配换行:
result = re.match(r'^He.*?(d+).*?Demo$',content,re.S)
1234567
分组
分组优先匹配组里面的内容,也只显示括号里面的。要想全部显示可以在括号前面加上(?:),进行去优先级:
>>> re.findall('www.(baidu|163).com', 'www.baidu.com')
['baidu']
# 在分组前面加上 ?: 表示去组的优先级
>>> re.findall('www.(?:baidu|163).com', 'www.baidu.com')
['www.baidu.com']
>>> re.findall('(abc)+', 'abcabcabc')
['abc']
>>> re.findall('(?:abc)+', 'abcabcabc')
['abcabcabc']
分组后的结果通过 group()
方法即可取到:
>>> re.match(r'^th.*?(d+).*?(d+)','this is 123456 regex 567')
<_sre.SRE_Match object; span=(0, 24), match='this is 123456 regex 567'>
>>> re.match(r'^th.*?(d+).*?(d+)','this is 123456 regex 567').group()
'this is 123456 regex 567'
>>> re.match(r'^th.*?(d+).*?(d+)','this is 123456 regex 567').group(1)
'123456'
>>> re.match(r'^th.*?(d+).*?(d+)','this is 123456 regex 567').group(2)
'567'
另一种分组方式,?P<组名>,通过组名取值,当匹配的结果有很多时,可以这样取值 :
# 另一种分组方式,?P<组名> ,通过组名取值,当匹配的结果有很多时,可以这样取值
>>> re.search("(?P<name>[a-z]+)", "rose18john20tom22")
<_sre.SRE_Match object; span=(0, 4), match='rose'>
>>> re.search("(?P<name>[a-z]+)", "rose18john20tom22").group()
'rose'
>>> re.search("(?P<name>[a-z]+)", "rose18john20tom22").group('name') # 名字分组为 name 'rose'
>>> re.search("(?P<name>[a-z]+)(?P<age>d+)", "rose18john20tom22").group('age') # 名字分组为 age '18'
re 模块常用方法
match 方法
match(patter, string, flags=0)
方法从字符串开始位置匹配,如果匹配成功则返回匹配位置以及 match 对象,否则返回 None。
第一个参数为正则表达式,第二个参数为待匹配的字符串。
>>> re.match('d', 'rose56')
>>> re.match('d', '12rose56')
<_sre.SRE_Match object; span=(0, 1), match='1'>
>>> re.match('d', '12rose56').group()
'1'
search 方法
search(pattern, string, flags=0)
方法扫描整个字符串,搜索正则表达式模式匹配的第一个位置,并返回 match 对象。如果没有与之匹配的,则返回 None。正则表达式模式即为匹配规则,需要用原生字符串来写,避免不必要的麻烦。
第一个参数为正则表达式,第二个参数为待匹配的字符串。
>>> re.search(r'abc', 'abc123abc')
<_sre.SRE_Match object; span=(0, 3), match='abc'> # 返回第一个匹配结果的位置,以及 match 对象
使用 group()
方法可以拿到匹配结果:
>>> result = re.search(r'abc', 'abc123abc')
>>> result.group()
'abc'
使用 span()
方法可以拿到匹配结果范围:
>>> result = re.search(r'abc', 'abc123abc')
>>> result.span()
(0, 3)
split 方法
split(pattern, string, maxsplit=0, flags=0)
分割字符串,返回结果存储到列表中,maxsplit 为早打分割次数。
>>> re.split(' ', 'hi hello six') # 按空格分
['hi', 'hello', 'six']
>>> re.split('[ |]', 'hi hello|six') # 按空格或 | 分
['hi', 'hello', 'six']
>>> re.split('[ab]', 'abc') # 先按 a 分(a的左边为空,因此分为 '' 和 'bc'),再按 b 分(b 的左边为空,因此分为 '' 和 'c'
['', '', 'c']
sub 方法
sub(pattern, repl, string, count=0, flags=0)
方法替换原字符串某个文本,第一个参数为正则,第二个为替换成的字符串,第三个为原字符串,第四个为替换次数。并返回替换后的字符串。
>>> re.sub(r'd+', 'A', 'abcd12df45')
'abcdAdfA'
>>> re.sub(r'd+', 'A', 'abcd12df45', 1)
'abcdAdf45'
subn()
方法可以返回替换次数:
>>> re.subn(r'd+', 'A', 'abcd12df45')
('abcdAdfA', 2)
compile 方法
前面我们都是直接在 re 方法中使用正则,re 模块提供了一个 compile()
方法可以将正则编译成正则对象,以便匹配规则可以重复使用。
pattern = re.compile(r'd+')
其他方式直接传入正则对象即可:
>>> content = '2018-11-17 17:18'
>>> pattern = re.compile(r'd{2}:d{2}')
>>> result = re.findall(pattern,content)
>>> result
['17:18']
finditer 方法
finditer(pattern, string, flags=0)
方法与 findall()
方法类似 ,不同的是前者将匹配结果封装成一个迭代器,而后者将全部匹配结果存为一个列表。
>>> ret = re.finditer('d+', 'ss123dd456')
>>> next(ret).group()
'123'
findall 方法
search()
方法返回匹配结果的第一个内容,要想获得所有匹配内容。就需要借助 findall(pattern, string, flags=0)
方法,它会搜索整个字符串,并将匹配结果存到一个列表中。若没有匹配成功,则返回一个空列表。
>>> re.findall(r'd+','12so45ch')
['12', '45']