正则表达式为为高级的文本模式匹配、抽取或文本形式的搜索和替换功能提供了基础。本文主要介绍python正则表达式的一些基础功能,掌握它也可以使得在python编程中处理字符串游刃有余。
1.简介
正则表达式是一些由字符和特殊符号组成的字符串,匹配一系列有相似特征的字符串。Python 通过标准库中的 re 模块来支持正则表达式。
2.特殊符号和字符
表示法 | 描述 | 示例 | 实例解释 |
literal | 匹配文本字符串的字面值 | this | 匹配this |
re1|re2 | 匹配正则表达式re1 或者 re2 | foo|bar | foo或者bar |
. | 匹配任何字符(除了\n 之外)(一个.一个字符) | b.b | bsb,b1b... |
^ | 匹配字符串起始部分 | ^Dear | 匹配所有Dear开头的字符 |
$ | 匹配字符串终止部分 | txt$ | 匹配以txt结尾的字符串 |
* | 匹配0 次或者多次前面出现的正则表达式 | 5* | 0个或多个5 |
+ | 匹配1 次或者多次前面出现的正则表达式 | 5+ | 1个或多个5 |
? | 匹配0 次或者 1 次前面出现的正则表达式 | 5? | 0个或1个5 |
{N} | 匹配N 次前面出现的正则表达式(精准) | X{3} | 3个X |
{N,} | 匹配N 次前面出现的正则表达式 | X{3} | 3个及以上X |
{M,N} | 匹配M~N 次前面出现的正则表达式 | X{2,5} | 2到5个X |
[…] | 匹配来自字符集的任意单一字符 | [aeiou] | a,e,i,o,u的任一个 |
[..x−y..] | 匹配x~y 范围中的任意单一字符 | [2-5], [D-F] | 2,3,4,5任一个,D,E,F任一个 |
[^…] | 不匹配此字符集中出现的任何一个字符,包括某一范围的字符 | [^aeiou],[^0-9] | 不匹配a,e,i,o,u任何一个字母,不匹配任一个数字 |
表一常见正则表达式符号
表示法 | 描述 | 示例 | 实例解释 |
\d | 匹配任何十进制数字,与[0-9]一致 | \d{11} | 表示11位数字 |
\D | 非数字字符,与\d 相反 | \D{5} | 5个非数字字符 |
\w | 匹配任何字母数字字符,与[A-Za-z0-9_]相同 | \w+ | 数字和字符构成的字符串均可匹配到 |
\W | 匹配非数字字母 | ||
\s,\n, \t, \r, \v, \f, 等 | 匹配任何空格字符,匹配一个换行符,匹配一个制表符,匹配回车,匹配垂直制表符,匹配换页符 | ||
\S | 匹配非空白符号 | ||
\b | 匹配任何单词边界 | er\b | 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er' |
\B | 匹配非单词边界(要包含) | er\B | 能匹配"verb" 中的 'er',但不能匹配 "never" 中的 'er' |
\A | 匹配整个字符串的开始,不支持多行模式 | \Aabc | 整个字符串abc开头匹配不上xyz\nabc |
\Z(大写) | 匹配字符串的结束,如果是存在换行,只匹配到换行前的结束字符串。 | def\Z | 整个字符串匹配以def结尾。匹配不上def\nxyz |
\z(小写) | 匹配字符串结束 | xyz\Z | 整个字符串匹配以xyz结尾。可以匹配def\nxyz |
\G | 匹配最后匹配完成的位置 |
表二常见正则表达式特殊字符
表示法 | 描述 | 示例 | 实例解释 |
(?#…) | 表示注释,所有内容都被忽略 | ||
(?=…) | 前向肯定断言 1、表达式一(?<=子组表达式)表达式二 2、表达式一可选,对前向肯定断言没有影响。首先匹配后面的表达式二捕获内容二,然后用子组表达式从开始位置匹配内容二(相当于子组表达式前面固定添加^),如果匹配成功,则断言成功,否则断言失败。断言成功整条表达式返回成功。断言失败整条表达式返回失败。 3、避免没有表达式二 | 1、re.search(r'(?=abc)abc',"fishc@abcch") 2、re.search(r'@(?=abc)abcch',"fishc@abcch") 3、re.search(r'(?=abc)@abc',"fishc@abcch") 4、re.search(r'fishc@abc(?=abc)ch',"fishc@abcch") 5、re.search(r'(?=.abc)@abc',"fishc@abcch") 6、re.search(r'fishc@(?=abc)',"fishc@abcch") 7、re.search(r'fish(?=abc)',"fishc@abcch") 8、re.search(r'(?=abc)',"fishc@abcch") | 1、span=(6, 9), match='abc' 2、span=(5, 11), match='@abcch' 3、None 4、None 5、span=(5, 9), match='@abc' 6、span=(0, 6), match='fishc@' 7、None 8、span=(6, 6), match='' |
(?!…) | 前向否定断言 1、表达式一(?<=子组表达式)表达式二 2、表达式一可选,对前向否定断言没有影响。首先匹配后面的表达式二捕获内容二,然后用子组表达式从开始位置匹配内容二(相当于子组表达式前面固定添加^),如果匹配成功,则断言失败,否则断言成功。断言成功整条表达式返回成功。断言失败整条表达式返回失败。 3、避免没有表达式二 | 1、re.search(r'(?!abc)@abc',"fishc@abcch") 2、re.search(r'fishc@abc(?=abc)ch',"fishc@abcch") 3、re.search(r'(?!abc)abc',"fishc@abcch") 4、re.search(r'@(?!abc)abcch',"fishc@abcch") 5、re.search(r'fishc@(?!abc)',"fishc@abcch") 6、re.match(r'fishc@(?!abc)',"fishc@abcch") 7、re.search(r'(?!abc)',"fishc@abcch") | 1、span=(5, 9), match='@abc' 2、span=(0, 11), match='fishc@abcch' 3、None 4、None 5、None 6、None 7、span=(0, 0), match='' |
(?<=…) | 后向肯定断言 1、表达式一(?<=子组表达式)表达式二 2、首先匹配表达式一捕获内容一,然后用子组表达式从结束位置匹配内容一(相当于子组表达式后面固定添加$),如果匹配成功,则断言成功,否则断言失败。断言成功如果有表达式二则执行表达式二,如果没有整条表达式返回成功。如果断言失败,则整条表达式返回失败。 3、避免没有表达式一 | 1、re.search(r'fishc@abc(?<=abc)',"fishc@abcch") 2、re.search(r'fishc@abcch(?<=abc)',"fishc@abcch" 3、re.search(r'fishc@(?<=abc)abc',"fishc@abcch") 4、re.search(r'fishc@abc(?<=abc)ch',"fishc@abcch" 5、re.search(r'(?<=abc)abc',"fishc@abcch") 6、re.search(r'(?<=abc)ch',"fishc@abcch") 7、re.search(r'(?<=abc)',"fishc@abcch") | 1、span=(0, 9), match='fishc@abc' 2、None 3、None 4、span=(0, 11), match='fishc@abcch' 5、None 6、span=(9, 11), match='ch' 7、span=(9, 9), match='' |
(?<!…) | 后向否定断言 1、表达式一(?<=子组表达式)表达式二 2、首先匹配表达式一捕获内容一,然后用子组表达式从结束位置匹配内容一(相当于子组表达式后面固定添加$),如果匹配成功,则断言失败,否则断言成功。断言成功如果有表达式二则执行表达式二,如果没有整条表达式返回成功。如果断言失败,则整条表达式返回失败。 3、避免没有表达式一 | 1、re.search(r'fishc@abcch(?<!abc)',"fishc@abcch") 2、re.search(r'fishc@(?<!abc)abc',"fishc@abcch") 3、re.search(r'fishc@abc(?<!abc)',"fishc@abcch") 4、re.search(r'fishc@abc(?<!abc)ch',"fishc@abcch") 5、re.search(r'(?<!abc)abcch',"fishc@abcch") 6、re.search(r'(?<!abc)shc',"fishc@aabcch") 7、re.search(r'(?<!abc)',"fishc@abcch") | 1、span=(0, 11), match='fishc@abcch' 2、span=(0, 9), match='fishc@abc' 3、None 4、None 5、span=(6, 11), match='abcch' 6、span=(2, 5), match='shc' 7、span=(0, 0), match='' |
表三常见正则表达式扩展表示法
优先级 | 符号 |
最高 | “\” |
高 | “()” “(?:)” “(?=)” “[]” |
中 | “*”“+” “?”“{n}” “{n,}” “{n,m}” |
低 | “^” “$” “中介字符” |
次最低 | 串接,即相邻字符连接在一起 |
最低 | “|” |
表四正则表达式特殊字符的优先级
正则表达式中有些字符具有特殊的含义,如果在匹配中要用到它本来的含义,需要进行转义,使用转义符'\'。而上述表二,可以看出有些字符自带反斜杠,如\d匹配数字。因此我们可以直接理解为: 如果'\'后面跟的字符不是ASCII数字或者ASCII字母,那么正则样式将直接匹配后面跟的字符,例如,.*\$$匹配任何以美元符号结尾的字符串。
3.正则表达式核心函数
Python 当前如何通过使用 re 模块来支持正则表达式,表五列出了来自 re 模块的更多常见函数。下面将介绍常用的函数
函数 | 描述 |
compile(pattern,flags = 0) | 使用任何可选的标记来编译正则表达式的模式,然后返回一个正则表达式对象 |
match(pattern,string,flags=0) | 尝试使用带有可选的标记的正则表达式的模式来匹配字符串。如果匹配成功,就返回匹配对象;如果失败,就返回None |
search(pattern,string,flags=0) | 使用可选标记搜索字符串中第一次出现的正则表达式模式。如果匹配成功,则返回匹配对象;如果失败,则返回None |
findall(pattern,string [, flags] ) | 查找字符串中所有(非重复)出现的正则表达式模式,并返回一个匹配列表 |
finditer(pattern,string [, flags] ) | 与findall()函数相同,但返回的不是一个列表,而是一个迭代器。对于每一次匹配,迭代器都返回一个匹配对象 |
split(pattern,string,max=0) | 根据正则表达式的模式分隔符,split 函数将字符串分割为列表,然后返回成功匹配的列表,分隔最多操作 max 次(默认分割所有匹配成功的位置) |
sub(pattern,repl,string,count=0) | 使用repl 替换所有正则表达式的模式在字符串中出现的位置,除非定义 count,否则就将替换所有出现的位置(另见 subn()函数,该函数返回替换操作的数目) |
purge() | 清除隐式编译的正则表达式模式 |
group(num=0) | 返回整个匹配对象,或者编号为num 的特定子组 |
groups(default=None) | 返回一个包含所有匹配子组的元组(如果没有成功匹配,则返回一个空元组) |
groupdict(default=None) | 返回一个包含所有匹配的命名子组的字典,所有的子组名称作为字典的键(如果没有成功匹配,则返回一个空字典) |
表五常用的函数
3.1 compile()
当我们在Python中使用正则表达式时,re模块内部会干两件事情:①编译正则表达式,如果正则表达式的字符串本身不合法,会报错;②用编译后的正则表达式去匹配字符串。
那么如果一个正则表达式要重复使用几千次,出于效率的考虑,是不是应该先把这个正则先预编译好,接下来重复使用时就不再需要编译这个步骤了,直接匹配,提高效率,compile()就事干这个的,在模式匹配发生之前,正则表达式模式必须编译成正则表达式。
re.compile(pattern,flags )
pattern为一个字符串形式的正则表达式,
flag可选,表示匹配模式,见表六。
re.I、re.IGNORECASE | 不区分大小写的匹配 |
re.L、re.LOCALE | 根据所使用的本地语言环境通过\w、\W、\b、\B、\s、\S 实现匹配 |
re.M、re.MULTILINE | ^和$分别匹配目标字符串中行的起始和结尾,而不是严格匹配整个字符串本身的起始和结尾 |
re.S、rer.DOTALL | “.”(点号)通常匹配除了\n(换行符)之外的所有单个字符;该标记表示 “.”(点号能够匹配全部字符 |
re.X、re.VERBOSE | 通过反斜线转义,否则所有空格加上#(以及在该行中所有后续文字)都被忽略,除非在一个字符类中或者允许注释并且提高可读性 |
表六常用的模块属性
例子:
import re
m_token=r'\d+'
pattern=re.compile(m_token)
m = pattern.match('12twothree34four')
print(m)
输出结果:
<_sre.SRE_Match object; span=(0, 2), match='12'>
3.2 match()
match() 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match() 就返回 none。
re.match(pattern,string,flags)
pattern 匹配的正则表达式
string 要匹配的字符串。
flags 可选,表示匹配模式,见表六。
例子:
import re
m_token=r'\d+'
pattern=re.compile(m_token)
m1 = pattern.match('12twothree34four')
m2 = pattern.match('abc12twothree34four')
print(m1)
print(m2)
输出结果:
<_sre.SRE_Match object; span=(0, 2), match='12'>
None
说明,只有字符串起始开始匹配成功,才不返回None。
3.3 search()
其实,想要搜索的模式出现在一个字符串中间部分的概率,远大于出现在字符串起始部分的概率。这也就是search()派上用场的时候了。search()的工作方式与 match()完全一致,不同之处在于 search()会用它的字符串参数,在任意位置对给定正则表达式模式搜索第一次出现的匹配情况。如果搜索到成功的匹配,就会返回一个匹配对象;否则,返回 None。
re.search(pattern,string,flags)
pattern 匹配的正则表达式
string 要匹配的字符串。
flags 可选,表示匹配模式,见表六。
例子:
import re
m_token=r'\d+'
pattern=re.compile(m_token)
m1 = pattern.search('12twothree34four')
m2 = pattern.search('abc12twothree34four')
print(m1)
print(m2)
输出结果:
<_sre.SRE_Match object; span=(0, 2), match='12'>
<_sre.SRE_Match object; span=(3, 5), match='12'>
说明:通过比较match(),发现search()只要整个字符串有匹配成功就返回。
3.4 group()和 groups()
成功调用match()或者search()返回的对象为匹配对象,匹配对象有两个主要的方法:group()和groups()。group()方法用于获得一个或多个分组匹配的字符串,要么返回整个匹配对象,要么根据要求返回特定子组。当要获得整个匹配的子串时,可直接使用 group() 或 group(0)。groups()则仅返回一个包含 唯一或者全部子组的元组,等价于(m.group(1), m.group(2), ...)。
例子:
import re
line = "Cats are smarter than dogs"
matchObj1 = re.match(r'(.*) are (.*?) .*', line, re.M | re.I)
matchObj2 = re.match(r'(.*) are (.*?) (.*)', line, re.M | re.I)
print("matchObj1.group() : ", matchObj1.group())
print("matchObj2.group() : ", matchObj2.group())
print("matchObj1.groups() : ", matchObj1.groups())
print("matchObj2.groups() : ", matchObj2.groups())
输出结果:
matchObj1.group() : Cats are smarter than dogs
matchObj2.group() : Cats are smarter than dogs
matchObj1.groups() : ('Cats', 'smarter')
matchObj2.groups() : ('Cats', 'smarter', 'than dogs')
说明:matchObj1和matchObj2的区别在于.*是否为要求返回的特定子组,
因此matchObj1.groups() 只有两个元素 ('Cats', 'smarter'),
matchObj1.groups() 有三个元素 ('Cats', 'smarter', 'than dogs')。
3.5 findall()
findall()在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果有多个匹配模式,则返回元组列表,如果没有找到匹配的,则返回空列表。
match()和 search()的不同之处在于,match 和 search 是匹配一次 findall 匹配所有。
re.findall(string,pos,endpos)
string : 待匹配的字符串。
pos : 可选参数,指定字符串的起始位置,默认为 0。
endpos : 可选参数,指定字符串的结束位置,默认为字符串的长度。
例子:
import re
pattern = re.compile(r'\d+') # 查找数字
result1 = pattern.findall('runoob 123 google 456')
result2 = pattern.findall('run88oob123google456', 0, 10)
print(result1)
print(result2)
输出结果:
['123', '456']
['88', '12']
例子:
import re
result = re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10')
print(result)
输出结果:
[('width', '20'), ('height', '10')]
说明:实例2为多个匹配模式,返回元组列表。
finditer()函数是一个与 findall()函数类似但是更节省内存的变体,在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回。
例子:
mport re
it = re.finditer(r"\d+","12a32bc43jf3")
print(it)
for a in it:
print(a)
输出结果:
<callable_iterator object at 0x0000021ED9214978>
<_sre.SRE_Match object; span=(0, 2), match='12'>
<_sre.SRE_Match object; span=(3, 5), match='32'>
<_sre.SRE_Match object; span=(7, 9), match='43'>
<_sre.SRE_Match object; span=(11, 12), match='3'>
说明:可以看出,it返回的是一个迭代器,可以提取匹配的内容。
3.6 sub()
sub()是将某字符串中所有匹配正则表达式的部分进行某种形式的替换。
re.sub(replacement, string, count)
replacement是被替换成的文本
string是需要被替换的文本
count是一个可选参数,指最大被替换的数量
例子:
import re
p = re.compile('(blue|white|red)')
print(p.sub('colour','blue socks and red shoes'))
print(p.sub('colour','blue socks and red shoes', count=1))
输出结果:
colour socks and colour shoes
colour socks and red shoes
说明:当添加了count=1只将blue替换成了colour,red没有被替换。
subn()方法执行的效果跟sub()一样,不过它会返回一个二维数组,包括替换后的新的字符串和总共替换的数量。
例子:
import re
p = re.compile('(blue|white|red)')
print(p.subn('colour','blue socks and red shoes'))
print(p.subn('colour','blue socks and red shoes', count=1))
输出结果:
('colour socks and colour shoes', 2)
('colour socks and red shoes', 1)
4.分组
分组就是用一对圆括号“()”括起来的正则表达式,匹配出的内容就表示一个分组。从正则表达式的左边开始看,看到的第一个左括号“(”表示第一个分组,第二个表示第二个分组,依次类推,需要注意的是,有一个隐含的全局分组(就是0),就是整个正则表达式。分完组以后,要想获得某个分组的内容,直接使用group(num)和groups()函数去直接提取就行。
例子:
import re
msg = '<html><h1>hello</h1></html>'
result = re.match(r'<([0-9a-zA-Z]+)><([0-9a-zA-Z]+)>(.+)</([0-9a-zA-Z]+)></([0-9a-zA-Z]+)>$', msg)
#result = re.match(r'<([0-9a-zA-Z]+)>(.+)</\1>$', msg)
print(result)
print(result.group(0))
print(result.group(1))
print(result.group(2))
print(result.group(3))
print(result.group(4))
print(result.group(5))
print(result.groups())
输出结果:
<_sre.SRE_Match object; span=(0, 27), match='<html><h1>hello</h1></html>'>
<html><h1>hello</h1></html>
html
h1
hello
h1
html
('html', 'h1', 'hello', 'h1', 'html')
说明:上述正则表达式中出现了5对(),因此分成了5组,通过groups()中5个元素也可看出。
命名分组:
(?P<name>正则表达式)#name是一个合法的标识符
引用分组:
(?P=name)
注意:P为大写!
例子:
import re
msg = '<html><h1>hello</h1></html>'
result = re.match(r'<(?P<fenzu>[0-9a-zA-Z]+)><([0-9a-zA-Z]+)>(.+)</([0-9a-zA-Z]+)></([0-9a-zA-Z]+)>', msg)
print(result)
print(result.groups())
输出结果:
<_sre.SRE_Match object; span=(0, 27), match='<html><h1>hello</h1></html>'>
('html', 'h1', 'hello', 'h1', 'html')
说明:([0-9a-zA-Z]+)写成(?P<fenzu>[0-9a-zA-Z]+),命名了一个分组fenzu,且仍是5个分组。
例子:
import re
msg = '<html><h1>hello</h1></html>'
result = re.match(r'<(?P<fenzu>[0-9a-zA-Z]+)><([0-9a-zA-Z]+)>(.+)</([0-9a-zA-Z]+)></(?P=fenzu)>', msg)
print(result)
print(result.groups())
输出结果:
<_sre.SRE_Match object; span=(0, 27), match='<html><h1>hello</h1></html>'>
('html', 'h1', 'hello', 'h1')
说明:将后面的([0-9a-zA-Z]+)写成了(?P=fenzu),引用了分组。但是此时,之前的第五个分组'html'因引用了第一个分组,因此只有4个分组。
5.贪婪和非贪婪
“贪婪”,尽可能多的匹配;“非贪婪”,尽可能少的匹配。默认情况下,正则表达式将进行贪婪匹配,加上?为将贪婪匹配模式转为非贪婪匹配模式。
例子:
import re
sentence = """You said "why?" and I say "I don't know"."""
print(re.findall(r'"(.*)"', sentence))
print(re.findall(r'"(.*?)"', sentence))
输出结果:
['why?" and I say "I don\'t know']
['why?', "I don't know"]
说明:贪婪匹配,从sentence中尽可能长的匹配两个””之间的内容,因此打印出整个字符串;加上了?,变成了非贪婪匹配,因此从sentence中尽可能短的匹配两个””之间的内容,匹配到了就输出,因此打印出两个匹配内容。