概述
可以用来判断某个字符串是否符合某种模式
- 比如是否是邮箱地址、网址、电话号码、身份证号
可以用来在文本中寻找并抽取符合某种模式的字符串
- 比如在某个文本中抽取出邮箱地址
- 比如在小说中,抽取孔明说的有提及曹操的话
定义:正则表达式是个某些字符有特殊含义的字符串
表示一种字符串的模式
"abc" #匹配"abc"
"b.?p.*k" #可以匹配"bapk","bp abx k"等多种符合b**p**k的样子
"\d{3}[a-zA-Z]+.(\d{2}|N/A)\s\1"#???
#可以使用相关函数求给定字符串和正则表达式的匹配情况
字符/组合 | 匹配的模式 | 正则表达式样例 | eg |
---|---|---|---|
. | 除‘\n’外的任意一个字符,包括汉字 (多行匹配方式下也能匹配‘\n’) | ‘a.b’ | ‘acb’.‘adb’… |
* | 量词。表示左边的字符必须出现0次或任意多次 | ‘a*b’ | ‘b’,‘aab’… |
? | 量词。表示左边的字符必须出现0次或1次 | ‘ka?b’ | ‘kb’,‘kaaab’ |
+ | 量词。表示左边的字符必须出现1次或更多次 | ‘ka+b’ | ‘kab’,‘kaab’ |
{m} | 量词。m是整数。表示左边的字符必须并且只能出现m次 | ‘ka{3}b’ | ‘kaaab’ |
{m,n} | 量词。m、n是整数。表示左边的字符必须并且至少出现m次,做多n次。n可以忽略不写,表示无上限 | ‘ka{1,3}b’ | ‘kab’,‘kaab’,‘kaaab’ |
\d | 一种数字字符,等价于[0-9] | ‘a\db’ | ‘a0b’,‘a1b’… |
\D | 一种非数字字符,等价于[ ^ \d ] [ ^ 0-9 ] | ‘a\Db’ | ‘acb’,‘a9b’… |
\s | 一种空白字符,比如空格,\t等 | ‘a\sb’ | ‘a b’,‘a\nb’… |
\S | 一种非空白字符 | ’a\Sb‘ | ‘akb’… |
\w | 一种单词字符,包括汉字或大小写字母,数字,下划线或其他语言的汉字 | ‘a\wb’ | ‘a_b’,‘a中b’… |
\W | 一种不是单词字符的字符 | ‘a\Wb’ | ‘a?b’… |
| | A|B表示能匹配A或能匹配B均能算匹配 | ‘ab|c’ | ‘ab’,‘c’ |
如果要在正则表达式中表示特殊字符本身,就应该在其前面加’ \ ';
正则表达式 | 匹配的字符串 |
---|---|
‘a\ $b’ | ‘a$b’ |
‘a\ *b’ | ‘a*b’ |
‘a\ [\ ]b’ | ‘a[]b’ |
‘a\ .*b’ | ‘ab’ ‘a.b’ ‘a…b’… |
‘a\ \ \ \b’ | ‘a\b’ (注意:此字符串长度为3,中间那个字符是’‘,即r’a\b’) |
r’a\ \b’ | r’a\b’ (r’a\b’等价于’a\ \b’) |
字符范围和量词
范围符号[] 和 量词
[XXX]用法:
- 此处必须出现一个某某范围内的字符
- 此处必须出现一个字符,但不可以是某某范围内的字符
[a2c] | 匹配’a’,‘2’,'c’之一 |
---|---|
[a-zA-Z] | 匹配任一英文字母 |
[\da-z?] | 匹配一个数字或小写英文字母或’?’ |
[^abc] | 匹配一个非’a’,‘b’,'c’之一 的字符 |
[^a-f0-3] | 匹配一个非英文字母’a’ 到’f’,也非数字’0’到’3’的字符 |
匹配汉字
汉字的unicode编码范围是4e00-9fa5(16进制),因此【\ u 4e00-\ u9fa5】表示一个汉字
正则表达式示例
"[1-9]\d*" #=>表示以[1-9]开头,\d可出现任意多次,故为正整数
"-[1-9]\d*" #为负整数
"-?[1-9]\d*|0" #=> -?表示负号可以出现0次或1次,以[1-9]开头,\d可出现任意多次
#=> 或者和0相匹配
#=> 合起来表示就是整数
"[1-9]\d*|0" #=>表示非负整数
"-?([1-9]\d*\.\d*[1-9]|0\.\d*[1-9]|0)" #=>左右两边都没有多余0的小数
"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" #=>邮箱格式
正则表达式的函数
re.match
import re
re.match(pattern,string,flags=0)
#=>从字符串string的 起始位置匹配 一个模式pattern
#=>flags 标志位,用于控制模式串的匹配方式
#=>如是否区分大小写,是否多行匹配等等。如 re.M|re.I 表示忽略大小写并且多行匹配
#成功则返回一个匹配对象,否则返回None
import re
def match(pattern,string):
x=re.match(pattern,string)
if(x!=None):
print(x.group()) #=>x.group是匹配上的字符串
else:
print("None")
match("a c","a cdkgh") #=>a c
match("abc","kabc")#=>None 虽然有abc,但不是在起始位置
match("\wabc","kabc")#=>kabc
match("a\tb*c","a\tbbbbcdec")#=>a bbbbc b可出现0次或任意多次,然后跟c
match("ab*c","ac")#=>ac
match("a\d+c","ac")#=>None 中间没有数字
match("a\d{2}c","a34c")#=>a34c
match("a\d{2,}c","a3474884c")#=>a3474884c
match(".{2}bc","bcbcdbc") #=>bcbc
match("ab.*","ab")#=>ab
match("ab.*","abcd")#=>abcd
match("\d?b.*","1bcd")#=>1bcd
match("\d?b.*","bbcd")#=>bbcd
match("a?bc.*","abbbcd") #=>None
match("a.b.*","abcd") #=>None .是必须有一个字符
match("a.b.*","aeb") #=>aeb
match("a.?b.*","aebcdf")#=>aebcdf
#a和b之间没字符或有任意一个字符均可
match("a.+b.*","aegsfb") #=>aegsfb
match("a.+b.*","abc")#=>None
match("a高.+k","a高大kcd")#=>a高大k
re.search
re.search(pattern,string,flags=0)
查找字符串中可以匹配成功的子串
若匹配成功,则返回匹配对象。若无法匹配,则返回None
与match的区别在于,可以不从开头开始匹配
import re
def search(pattern,string):
x=re.search(pattern,string)
if(x!=None):
print(x.group(),x.span()) #=>输出子串以及起止位置
else:
print("None")
search("a.+bc*","dbaegsfbcef") #=>aegsfbc (2,9)
search("a.+bc*","bcdbaegsfbccc") #=>aegsfbccc (4,13)
search("a.?bc*d","dabccdc") #=>abccd (1,6)
search("aa","baaaa") #=>aa (1,3)
search("\([1-9]+\)","ab123(0456)(789)45ab") #=>(789) (11,16)
search("[1-9]\d+","ab01203d45")#=>1203 (3,7)
re.findall
- re.findall(pattern,string,flags=0)
- 查找字符串中**所有和模式匹配的子串(不重叠)**放入列表。
- 若一个匹配子串都没有,则返回空列表
import re
print(re.findall('\d+',"this is 334 what me 774gw"))
#=>['334','774']
print(re.findall('[a-zA-Z]+',"A dog has 4 legs.这是true"))
#=>['A','dog','has','legs','true']
print(re.findall('\d+',"this is good."))
#=>[]
print(re.findall("aaa","baaaa"))
#=>['aaa']
re.finditer
- re.finditer(pattern,string,flags=0)
- 查找字符串中所有和模式匹配的子串(不重叠)
- 每个子串对应与一个匹配对象
- 返回匹配对象的序列
import re
s = '233[32]88ab<433>(21)'
m = '\[\d+\]|<\d+>' #=>[32] <433>
for x in re.finditer(m,s):
print(x.group(),x.span())
i = 0
for y in re.finditer(m,"aaaaa"): #=>匹配对象序列为空
i += 1
re.sub
- re.sub(模式串,替换串,母串)
- 用于替换匹配的子串
import re
str=re.sub('\d+',"...","abc13de4fg")
print(str) #=>abc...de...fg
print(re.sub('\d+',"","abc13de4fg"))#=>abcdefg
print(re.sub('gone','go','I gone hegone me'))#=>I go hego me
边界符号
- \A 表示字符串的左边界,即要求从此往左边不能有任何字符
- \Z 表示字符串的右边界,即要求从此往右边不能有任何字符
- ^ 与\A同。但多行匹配模式下还可以表示一行文字的左边界
- $ 与\Z同。但多行匹配模式下还可以表示一行文字的右边界
- \b 表示此处应为单词的左边界或右边界,即不可是单词字符
- \B 表示此处不允许是单词的左边界或右边界,即必须是单词字符
- 边界符号本身不会和任何字符匹配。
注意
- 正则表达式的边界符号’\b’ 是两个字符。但是在Python字符串中,‘\b’和’\t’,'\n’类 似,是一个字符(Backspace)。
- 因此在正则表达式中使用边界符号\b,要写 ’ \ \b’ 。如果写 ‘\ \ \ \b’,则连续的两个’‘被看作是一个普通的’‘,不会和后面的’b’一起被 当作字符组合,变成边界符号’\b’。
- print(“\b”) #>>\b
import re
def search(pattern,string):
x=re.search(pattern,string)
if(x!=None):
print(x.group(),x.span()) #=>输出子串以及起止位置
else:
print("None")
pt="ka\\b.*" #=>表示a的右边不能是单词字符
search(pt,"ka") #=> ka
search(pt,"kax") #=> None
search(pt,"ka?d") #=>ka?b
pt = ".*\\bka\\b" #=>k的左边不能是单词字符,a的右边也不能是单词字符
search(pt,"ka") #=>ka
search(pt,"ska?") #=>None
search(pt,"b?ka?") #=>b?ka?
import re
def search(pattern,string):
x=re.search(pattern,string)
if(x!=None):
print(x.group(),x.span()) #=>输出子串以及起止位置
else:
print("None")
m = r"\bA.*N\b T" #等价于 m = "\\bA.*N\\b T"
search(m,"Ass$NC TK") #=>None
search(m,"this Ass$N TK") #=>Ass$N T
m = "\BA.*N\B\w T"
search(m,"this Ass$N TK") #=>None
search(m,"thisAss$NM TK") #=>Ass$NM T
search(m,"Ass$NM TK") #=>None
search(m,"xAss$NM TK") #=>Ass$NM T
pt = "\\b高兴"
search(pt,"我高兴") #=>None
search(pt,"我 高兴") #=>高兴
分组
形式:(…)
- 括号中的表达式是一个分组。多个分组按左括号从左到右从1开始依次编号
import re
x = re.search('[a-z]+(\d+)[a-z]+',"ab 123d hello553world47")
print(x.group())#=>hello553world 完整的匹配对象
print(x.group(1))#>>553 一号分组内容,忽略掉其他
m = "(((ab*)c)d)e"
r = re.match(m,"abcdefg")
print(r.group(0)) #=> 0号分组是 完整的匹配对象 abcde
print(r.group(1)) #=> 1号分组 abcd
print(r.group(2)) #=> abc
print(r.group(3)) #=> ab
print(r.groups()) #=> 所有分组情况,按元组的类型给出
#=>('abcd', 'abc', 'ab')
import re
m = "(ab*)(c(d))e"
r = re.match(m,"abcdefg")
print(r.groups())#>>('ab', 'cd', 'd')
print(r.group(0)) #>>abcde
print(r.group(1)) #>>ab
print(r.group(2)) #>>cd
print(r.group(3)) #>>d
在分组的右边可以通过分组的编号引用该分组所匹配的子串
import re
m = r'(((ab*)c)d)e\3'#r表示字符串里的'\'不再转义
#要求 ab*cde后面跟着3号分组在本次匹配中匹配上的子串
r = re.match(m,"abbbcdeabbbkfg")
# 红色部分少一个b则不能匹配
print(r.group(3))# abbb
print(r.group())# abbbcdeabbb
如果后面没有三号分组可匹配的内容,就会报错
import re
pt = 'a(.)\\1*b' #或 pt = r'a(.)\1*b'
#>>1号分组匹配上的是c
print(re.search(pt,'kacccccb').group()) #>>acccccb
#>>1号分组匹配上的是x
print(re.search(pt,'kaxxxxb').group()) #>>axxxxb
#>>1号分组匹配上的是x
print(re.search(pt,'kaxb').group())#>>axb
x = re.search(pt,'kaxyb') #>>None
if x != None:
print(x.group())
- 分组作为一个整体,后面可以跟量词
import re
m = "(((ab*)+c)d)e" # *号是0次或任意多次
r = re.match(m,"ababcdefg")
print(r.group()) #>>ababcde
print(r.groups()) #>>('ababcd', 'ababc', 'ab')
r = re.match(m,"abacdefg")
print(r.group()) #>>abacde!!!
print(r.groups()) #>>('abacd','abac','a')
#=>以最后一次匹配的为准
# 分组的量词化,并不要求每次分组匹配都一样
# 比如这里,第一次匹配出ab,第二次匹配出a =>均符合ab*
re.findall和分组
- 在正则表达式中没有分组时,re.findall返回所有匹配子串构成的列表。
- 有且只有一个分组时,re.findall返回的是一个子串的列表,每个元素是一个匹配子串中分组对应的内容。
import re
//存在分组
m = '[a-z]+(\d+)[a-z]+'
x = re.findall(m,"13 bc12de ab11 cd320ef")
print(x) #>>['12', '320']
//不存在分组
m='[a-z]+\d+[a-z]+'
x = re.findall(m,"13 bc12de ab11 cd320ef")
print(x) #>>['bc12de', 'cd320ef']
- 在正则表达式中有超过一个分组时
- re.findall返回的是一个元组的列表, 每个元组对应于一个匹配的子串,元组里的元素,依次是1号分组、2号分组、3号分组…匹配的内容
import re
m = '(\w+) (\w+)'
r = re.match(m,"hello world")
print(r.group())#>>hello world
print(r.groups())#>>('hello', 'world')
print(r.group(1)) #>>hello
print(r.group(2)) #>>world
r = re.findall(m,"hello world, this is very good")
#找出由所有能匹配的子串的 groups() 构成的元组,互相不重叠
print(r) #>>[('hello', 'world'), ('this', 'is'), ('very', 'good')]
|的用法
- 表示“或”
- 如果没有放到分组“()”里,则作用范围是直到整个正则表达式开头或结尾或另一个“|”
- “\w{4}ce|c\d{3}|p\w”
- 可以匹配:
- “c773” #满足c\d{3}
- “ab12ce” #满足\w{4}ce
- “pk” #满足p\w
- 从左往右短路匹配
- 匹配上一个后就不计算是否还能匹配后面的
import re
pt = "\d+\.\d+|\d+"
print(re.findall(pt,"12.34 this is 125")) #>>['12.34', '125']
pt = "aa|aab"
print(re.findall(pt,"aabcdeaa12aab"))#>>['aa', 'aa', 'aa']
- 用于分组:作用域是分组内
import re
m ="(((ab*)+c|12)d)e"
tmp=re.search(m,'ababcdefgKK12deK')
print(tmp.group())#=>ababcde 12de
print(re.findall(m,'ababcdefgKK12deK'))
#>>[('ababcd', 'ababc', 'ab'), ('12d', '12', '')]
for x in re.finditer(m,'ababcdefgKK12deK'):
#=finditer返回的是匹配对象的序列
print(x.groups())
#>>('ababcd', 'ababc', 'ab') #>>('12d', '12', None)
m = '\[(\d+)\]|<(\d+)>'
for x in re.finditer(m,'233[32]88ab<433>'):
print(x.group(),x.groups())
#>>[32] ('32', None) #=><433> (None, '433')
贪婪匹配模式
- 量词+ * ? {m,n} 默认匹配尽可能长的子串
import re
print(re.match("ab*","abbbbbk").group()) #>>abbbbb
#贪婪模式下存在的问题
print(re.findall("<h3>(.*)</h3>","<h3>abd</h3><h3>bcd</h3>"))
#=> findall+分组,输出的列表是分组内的内容
#>> ['abd</h3><h3>bcd'] 贪婪模式下,认为这一串都是中间的任意字符
print(re.findall("\(.+\)","A dog has(have a).这(哈哈)true ()me"));
#>>['(have a).这(哈哈)true ()']
懒惰匹配模式
- 量词+ * ? {m,n} 后面加‘?’ 则匹配尽可能短的子串
import re
m = "a.*?b"
for k in re.finditer(m,"aabab"):
print(k.group(),end=" ")
#>>aab ab
m = "<h3>.*?</h3>"
a = re.match(m,"<h3>abd</h3><h3>bcd</h3>")
print(a.group())
#>><h3>abd</h3>
print(re.findall(m,"<h3>abd</h3><h3>bcd</h3>"))
#>>['<h3>abd</h3>', '<h3>bcd</h3>']
m = "<h3>.*?[M|K]</h3>" #[M|K]存在M或K
a = re.match(m,"<h3>abd</h3><h3>bcK</h3>")
print(a.group())
#>><h3>abd</h3><h3>bcK</h3>
匹配对象
匹配成功时返回结果(称为匹配对象)的属性
string: 匹配时使用的母串
lastindex: 最后一个被匹配的分组的编号(不是最大编号)。如果没有被匹配的分组,就None
group: 获得一个或多个分组匹配的字符串;指定多个参数时将以元组的形式返回
group(1,2,3)
匹配了多次的分组,返回最后一次匹配的子串
group():以元组的形式返回全部分组匹配的字符串
groupdict(): 返回以有名字的组的名字为键,以该组匹配的子串为值的字典
start([group]): 返回指定的组匹配的子串在string中的起始位置。参数缺省值为0
end([group]):返回指定的组匹配的子串在string中的结束位置。参数缺省值为0
span([group]):返回-(start([group],end([group])) 缺省为0
import re
m = re.match(r'(\w+) (\w+)(.)', 'hello world!ss')
print( m.string)
#返回匹配对象的母串
#>>hello world!ss
print(m.lastindex)
#返回最后一个匹配上的分组的编号。因为分组可以嵌套,并不能说明一定是最大编号
#>>3
print(m.group(0,1,2,3))
#0返回的所有分组的内容
#>>('hello world!', 'hello', 'world', '!')
print(m.groups())
#等效于m.group(0)
#>>('hello', 'world', '!')
print(m.start(2))
#>>6
print(m.end(2))
#>>11
print(m.span(2))
#>>(6, 11)
应用实例
import re
f=open("文件地址","r",encoding="utf-8")
txt=f.read()
f.close()
pt="(孔明.{0,2}曰:“[^” ]*(曹操|曹贼|操贼|曹阿瞒|操).*?)"
a=re.findall(pt,txt)
print(len(a)) #>>58
for x in a:
print(x[0])
import re
ipadr = r"\b((25[0-5]|2[0-4]\d|((1\d{2})|([19]?\d)))\.){3}(25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\b"
mailbox = r"\b[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_]+)+\b"
url = r'http://[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][a-zA-Z0-9]{0,62})+(/[-a-zA-Z0-9]+)*\b'
s = "My ip is 223.44.3.4, this is http://www.pku.edu.cn/python/new, http://www.sohu.com my mailbox is guo_wei@pku.edu.cn. ok?"
m = re.search(ipadr,s)
if m != None:
print(m.group()) #>>223.44.3.4
m=re.search(mailbox,s)
if m!=None:
print(m.group())#>>guo_wei@pku.edu.cn
for x in re.finditer(url,s):
print(x.group())
#>>http://www.pku.edu.cn/python/new
#>>http://www.sohu.com
要想写出完美的正则表达式时很困难的。
可以考虑写得宽容一点。例如,ip地址一定能匹配它,但是匹配它的不一定是ip地址
后续用split以后手动判断每一段是不是小于225