13.python内置模块之re模块

时间:2022-08-28 00:22:50
13.python内置模块之re模块

什么是正则?

正则表达式也称为正则,是一个特殊的字符序列,能帮助检查一个字符串是否与某种模式匹配。可以用来进行验证:邮箱、手机号、qq号、密码、url = 网站地址、ip等。正则不是python语言独有的技术,python语言直到1.5版本才将正则表达式完成的整理/纳入进re模块中,我们只需要导入re模块,然后就可以使用其中所有和正则相关的函数和属性了。

1. re模块中最常用的几个函数

1). re.match函数:

    功能:将string数据从头开始尝试匹配 ;如果匹配成功,那么就会返回给程序一个match对象;如果开头就不匹配,那么直接返回None值;

    语法格式:re.match(regex, string[, flags=0])

    参数:regex:匹配的正则表达式(内部定义了一套验证规则)

string:需要被验证的字符串数据

flags:可选参,模式/标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。默认情况下(不显示定义) --> 不开启任何的模式

【注意】匹配成功re.match方法返回一个匹配的对象,否则返回None。我们可以使用group(num) 或 groups() 等匹配对象函数来获取匹配表达式。match对象有5个常用的函数:

(1). group():返回匹配成功的数据值(理解:原串中的某部分子串信息)

   (2). start():返回匹配成功的数据的起始索引

   (3). end():  返回匹配成功的数据的结束索引

   (4). span(): 返回一个元祖对象,有两个元素组成;第一个元素记录了匹配成功的数据的起始索引第二个元素记录了匹配成功的数据的结束索引

   (5). groups():以元祖的形式返回所有子组的信息(一个包含所有小组字符串的元组,从1到所含的小组号);如果没有进行正则分组,则返回一个空元祖。

 1 import re
2
3 mo = re.match(r'Www','www.baidu.com')
print(mo) # None
print(type(mo)) # <class 'NoneType'> print(re.match(r'www','www.sina.com')) # <_sre.SRE_Match object; span=(0, 3), match='www'>
print(re.match(r'www','www.sina.com').group()) # www
print(re.match(r'www','www.sina.com').start()) #
print(re.match(r'www','www.sina.com').end()) #
print(re.match(r'www','www.sina.com').span()) # (0,3)
print(re.match(r'www','www.sina.com').groups()) # ()
print(re.match(r'WWW','www.sina.com')) # None
print(re.match(r'WWW','www.sina.com',flags=re.I)) # <_sre.SRE_Match object; span=(0, 3), match='www'> line = "Cats are smarter than dogs" matchObj = re.match( r'(.*) are (.*?) .*', line, re.M|re.I) if matchObj:
print(matchObj.group()) # "Cats are smarter than dogs"
print(matchObj.group(1)) # "Cats"
print(matchObj.group(2)) # "smarter"
print(matchObj.groups()) # ("Cats","smarter")
else:
print("No match!!")

【补充】:第二种方式实现正则对数据的校验:re.compile函数

   功能:compile 函数用于编译正则表达式,生成一个正则表达式( Pattern )对象,供 match() 和 search() 这两个函数使用。

 语法格式为:re.compile(regex[, flags])

参数:和match一样理解

【注意】compile 函数执行完毕以后返回给程序一个Pattern对象(理解:对象的内部封装了一套regex和flags),可以再通过Pattern对象调用其match函数(此时的match函数只需要传递一个参数:string即可)

compile()配合match()使用演示:

 import re

 pat = re.compile(r'www',flags=re.I)
print(pat)   # re.compile('www', re.IGNORECASE)
print(type(pat))) # <class '_sre.SRE_Pattern'>
print(pat.match('www.baidu.com')) # <_sre.SRE_Match object; span=(0, 3), match='www'> # 相当于 re.compile.match(r'www','www.baidu.com',flags=re.I)

【注意事项】:

1).正则表达式返回的索引值需要满足含头不含尾的特点

2).正则表达式验证的数据内容严格区分大小写

 3).之后在定义正则表达式的时候,在它的第一个引号前面,都显示的追加一个r,无脑行为...(转义字符)

2). re.search函数:

 功能:从头开始尝试匹配,如果开头就匹配不成功,不会返回None值,会继续尝试往后匹配;一旦匹配成功了,就直接返回一个match对象,后续就算还存在可以匹配成功的子串数据,也不会再匹配了(直接无视);如果直到最后都匹配不成功,返回一个None值。

   语法格式:re.search(regex,string[,flags=0])

 参数:和match一样理解

【补充】:由于search函数调用返回的是match对象,所以仍然可以调用5个常用的函数

 import re

 print(re.match(r'www','hahawww.baidu.com!!www.qfedu.comhehe'))              # None
print(re.search(r'www','hahawww.baidu.com!!www.qfedu.comhehe')) # <_sre.SRE_Match object; span=(4, 7), match='www'>
print(re.search(r'www','hahawww.baidu.com!!www.qfedu.comhehe').group()) # www
print(re.search(r'www','hahawww.baidu.com!!www.qfedu.comhehe').start()) #
print(re.search(r'www','hahawww.baidu.com!!www.qfedu.comhehe').end()) #
print(re.search(r'www','hahawww.baidu.com!!www.qfedu.comhehe').span()) # (4, 7)
print(re.search(r'www','hahawww.baidu.com!!www.qfedu.comhehe').groups()) # ()
print(re.search(r'Www','hahawww.baidu.com!!www.qfedu.comhehe')) # None
print(re.search(r'Www','hahawww.baidu.com!!www.qfedu.comhehe',flags=re.I)) # <_sre.SRE_Match object; span=(4, 7), match='www'>

compile()配合search()使用演示:

1 import re
2
3 pat = re.compile(r'www')
4 mo = pat.search('Wwww.sina.com!!www.baidu.com!!www')
5
6 print(mo) # <_sre.SRE_Match object; span=(1, 4), match='www'>

3). re.findall函数:

 功能:在字符串中找到所有匹配成功的子数据(子串),都存入到列表中返回;如果一个都匹配不成功,那么返回一个空列表。

 语法格式:re.findall(regex,string[, pos[, endpos]][,flags=0])

 参数:pos:可选参数,指定字符串的起始位置,默认为 0

endpos :可选参数,指定字符串的结束位置,默认为字符串的长度

         其他参数和match、search一样理解

 import re

 lt = re.findall(r'www','www.sina.com!!www.baidu.com')
print(lt,type(lt)) # ['www', 'www'] <class 'list'> lt1 = re.findall(r'WWW','www.sina.com!!www.baidu.com!!WWW',flags=re.I)
print(lt1,type(lt1)) # ['www', 'www', 'WWW'] <class 'list'>

compile()配合search()使用演示:

 import re

 pat = re.compile(r'www',flags=re.I)
lt = pat.findall('www.sina.com!!www.baidu.com!!WWW') print(lt) # ['www', 'www', 'WWW'] pattern = re.compile(r'\d+') # 查找数字
result1 = pattern.findall('runoob 123 google 456') # 不指定字符串的起始和结束位置
result2 = pattern.findall('run88oob123google456', 0, 10) # 指定字符串的起始和结束位置 print(result1) # ['123', '456']
print(result2) # ['88', '12']

4). re.finditer函数:

 功能:和 findall 类似,在字符串中找到正则表达式所匹配的所有子串,但是将所有匹配成功的数据封装为一个一个的match对象, 然后以iterator(迭代器对象)返回

    语法格式:finditer(regex,string[,flags=0])

    参数:和match、search、findall一样理解

 import re

 it = re.finditer('apple', 'i hate apple apple apple  so much')

 # iterator(迭代器对象)不能直接使用内置函数len()执行查看其容量大小,会报错:TypeError; 但是可以先将其转换为容器对象(list、tuple...),就可以被len()所执行了;
print(len(it)) # TypeError
print(len(list(it))) # # 使用遍历的思想去访问iterator中的元素
for i in it:
print(i)
print(type(i))
print(i.group()) # <_sre.SRE_Match object; span=(7, 12), match='japan'>
# <class '_sre.SRE_Match'>
# apple # <_sre.SRE_Match object; span=(13, 18), match='japan'>
# <class '_sre.SRE_Match'>
# apple # <_sre.SRE_Match object; span=(19, 24), match='japan'>
# <class '_sre.SRE_Match'>
# apple #迭代器对象中的内容只能被使用一次,不可逆,否则报错:StopIteration
# print(next(it)) # 使用while循环来遍历iterator对象,结果和for......in..一样
while 1:
try:
mo = next(it)
print(mo)
print(mo.group())
except:
break

5). re.sub 函数:

   功能:替换字符数据

 语法格式:sub(regex,repl,string,[count],[flags=0]):返回字符串(已经被替换完成后的内容)

subn(regex,repl,string,[count],[flags=0]):返回元祖对象,此对象有两个元素;第一个元素记录了替换以后的字符串内容,第二个元素记录了被替换的次数(count)

参数:regex:正则规则(字符串)

     repl:替换成的字符串内容,也可为一个函数

       string:原串数据

       count:次数,模式匹配后替换的最大次数,默认 为0 表示替换所有的匹配

 import re 

 str1 = re.sub('beijing', 'shanghai', 'i love beijing beijing beijing so much')
print(str1,type(str1)) # i love shanghai shanghai shanghai so much <class 'str'> tp = re.subn('beijing', 'shanghai', 'i love beijing beijing beijing so much', 2) # 2改成4也不会报错,就会全部替换
print(tp,type(tp)) # ('i love shanghai shanghai beijing so much', 2) <class 'tuple'> # 示例:模拟让游戏世界变得和谐(使用正则)
regex = r'CNM|MB|SB|NC|WQNMLGB|TMD|NND'
game = 'WQNMLGB!!连装备都不会出...小学生!!SB...' s = re.sub(regex,'***',game)
print(s) # ***!!连装备都不会出...小学生!!***... # 示例:删除某些内容
phone = "2004-959-559 # 这是一个国外电话号码" # 删除字符串中的Python注释
num = re.sub(r'#.*$', "", phone)
print("电话号码是: ", num) # 电话号码是: 2004-959-559 # 删除非数字(-)的字符串
num = re.sub(r'\D', "", phone)
print("电话号码是 : ", num) # 电话号码是 : 2004959559 # 示例:repl参数是一个函数时,将字符串中的匹配的数字乘以 2
# 将匹配的数字乘以 2
def double(matched):
value = int(matched.group('value'))
return str(value * 2) s = 'A23G4HFD567'
print(re.sub('(?P<value>\d+)', double, s)) # A46G8HFD1134

【补充示例】:替换相关的操作

 str5 = 'dsaf######32141asf#####dsafa#########()!,.___######21341##'

 # 将一个#替换成为一个-
regex1 = r'#'
str6 = re.sub(regex1,'-',str5)
print(str6) # dsaf------32141asf-----dsafa---------()!,.___------21341-- # 将一堆#替换成为一个-
regex2 = r'#+'
tp = re.subn(regex2,'-',str5)
print(tp) # ('dsaf-32141asf-dsafa-()!,.___-21341-', 5)

6). re.split 函数:

   功能:按照能够匹配的子串将字符串分割后返回列表

   语法格式:re.split(regex, string[, maxsplit=0, flags=0])

   参数:maxsplit :分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数

 import re 

 str1 = 'i       love        shanghai    so  much'
regex = r' +' # 切割空格,+表示一个到多个,有贪婪行为,把空格全切了
lt = re.split(regex,str1)
print(lt) # ['i', 'love', 'shanghai', 'so', 'much'] str2 = 'i love shanghai so much'
regex = r' +?' # 切割空格 +表示一个到多个, ?取消贪婪行为,能少切就少切
lt = re.split(regex,str2)
print(lt) # ['i', '', '', '', '', '', '', 'love', '', '', '', '', '', '', '', 'shanghai', '', '', '', 'so', '', 'much'] str3 = 'dasfas23141sfa123dsafas13dasdfa1231241____3241234^&*&(dsafa
214331asdfa'
regex = r'\d+?'
lt = re.split(regex,str3)
print(lt) # ['dasfas', '', '', '', '', 'sfa', '', '', 'dsafas', '', 'dasdfa', '', '', '', '', '', '', '____', '', '', '', '', '', '', '^&*&(dsafa', '', '', '', '', '', 'asdfa'] regex1 = r'\d+'
lt1 = re.split(regex1,str3)
print(lt1) # ['dasfas', 'sfa', 'dsafas', 'dasdfa', '____', '^&*&(dsafa', 'asdfa']

2. 正则表达式修饰符 - 可选标志(flags)

正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。多个标志可以通过按位 OR(|) 它们来指定。如 re.I | re.M 被设置成 I 和 M 标志:

re.I:使匹配对大小写不敏感,忽略大小写

re.L:做本地化识别(locale-aware)匹配,表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境

re.M:多行匹配,影响 ^ 和 $,多行模式

re.S:使.匹配包括换行在内的所有字符

re.U:根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B

re.X:该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解,即为了增加可读性,忽略空格和 # 后面的注释

3. 正则中元字符的使用

正则表达式模式:模式字符串使用特殊的语法来表示一个正则表达式:字母和数字表示他们自身。一个正则表达式模式中的字母和数字匹配同样的字符串。多数字母和数字前加一个反斜杠时会拥有不同的含义。标点符号只有被转义时才匹配自身,否则它们表示特殊的含义。反斜杠本身需要使用反斜杠转义。由于正则表达式通常都包含反斜杠,所以你最好使用原始字符串来表示它们。模式元素(如 r'\t',等价于 '\\t')匹配相应的特殊字符。下表列出了正则表达式模式语法中的特殊元素。如果你使用模式的同时提供了可选的标志参数,某些模式元素的含义会改变。

匹配单个字符(数字、英文、其它...)符号位:

[ ]表示一字符位

[9876123450]表示一位,取值范围:[0,9]中间的任何一位值

[0-9]表示一位,取值范围:[0,9]中间的任何一位值

\d表示一位,取值范围:[0,9]中间的任何一位值

\D对以上的\d进行取反,意味着:匹配除了数字字符以外的所有字符

[24680]表示一位,取值范围:2、4、6、8、0中的任何一个值

[abcdefg]表示一位,取值范围:a、b、c、d、e、f、g中的任何一个值

[a-z]表示一位,取值范围:[a,z]中的任何一个值

[A-Z]表示一位,取值范围:[A,Z]中的任何一个值

[0-9a-zA-Z_]表示一位,取值范围:0~9、a~z、A~Z以及_中的任何一个值

\w表示一位,取值范围:0~9、a~z、A~Z以及_中的任何一个值

\W对\w进行取反操作,意味着:匹配除了0~9、a~z、A~Z以及_中的其它所有字符

. 匹配除了换行符以外的所有字符

代码演示:

 import re

 reg = r'[12345]'
print(re.match(reg,'')) # <_sre.SRE_Match object; span=(0, 1), match='4'>
print(re.match(reg,'')) # <_sre.SRE_Match object; span=(0, 1), match='4'>
print(re.match(reg,'')) # None reg = r'[0-9]' # 两者作用一样reg = r'\d'
print(re.match(reg,'')) # <_sre.SRE_Match object; span=(0, 1), match='8'> reg = r'\d\d'
print(re.match(reg,'')) # <_sre.SRE_Match object; span=(0, 2), match='84'> reg = r'\D\d'
print(re.match(reg,'a842')) # <_sre.SRE_Match object; span=(0, 2), match='a8'> reg = r'\w\d[a-z][0-9]' # 一共要匹配四位数据
print(re.match(reg,'_6a842')) #<_sre.SRE_Match object; span=(0, 4), match='_6a8'>
print(re.findall(reg,'_6a842')) # ['_6a8']
print(re.findall(reg,'_6a842c7')) # ['_6a8', '42c7'] reg1 = r'.'
print(re.match(reg1,'_6a842')) # <_sre.SRE_Match object; span=(0, 1), match='_'> reg2 = r'..'
print(re.match(reg2,'_6a842')) # <_sre.SRE_Match object; span=(0, 2), match='_6'>
print(re.match(reg2,'\n_6a842')) # None
print(re.search(reg2,'\n_6a842')) # <_sre.SRE_Match object; span=(1, 3), match='_6'>
print(re.findall(reg2,'\n_6a842')) # ['_6', 'a8', '42']

匹配锚字符(边界字符):

^从字符串数据的头部开始匹配,在开启了多行模式的情况下(re.M),它可以尝试匹配每一行的头部数据

$从字符串数据的尾部开始匹配,在开启了多行模式的情况下(re.M),它可以尝试匹配每一行的尾部数据

\A从字符串数据的头部开始匹配,在开启了多行模式的情况下(re.M),它没有多行的概念,还是匹配第一行的头部数据

\Z从字符串数据的尾部开始匹配,在开启了多行模式的情况下(re.M),它没有多行的概念,还是匹配最后一行的尾部数据

补充:

\b尝试匹配边界(左侧、右侧)数据,如果一旦满足返回对象(match、list)

\B先舍弃规定边界的数据,然后一定满足从左侧开始匹配数据,...

代码演示:

 import re 

 print(re.search(r'^www','hahawww.qfedu.comhehe'))       # None,search改成match效果一样
print(re.search(r'^haha','hahawww.qfedu.comhehe')) # <_sre.SRE_Match object; span=(0, 4), match='haha'>,search改成match效果一样
print(re.search(r'hehe$','hahawww.qfedu.comhehe')) # <_sre.SRE_Match object; span=(17, 21), match='hehe'>
print(re.search(r'.com$','hahawww.qfedu.comhehe')) # None print(re.findall(r'hehe','hahawww.heheqfedu.comhehe')) # ['hehe', 'hehe']
print(re.findall(r'hehe$','hahawww.heheqfedu.comhehe')) # ['hehe'] print(re.search(r'\Awww','hahawww.qfedu.comhehe')) # None
print(re.search(r'\Ahaha','hahawww.qfedu.comhehe')) # <_sre.SRE_Match object; span=(0, 4), match='haha'>
print(re.search(r'hehe\Z','hahawww.qfedu.comhehe')) # <_sre.SRE_Match object; span=(17, 21), match='hehe'>
print(re.search(r'.com\Z','hahawww.qfedu.comhehe')) # None

演示匹配边界字符单行与多行的区别:

 str1 = '''Tom is a good man
Tom is a nice man
Tom is a cool man
Tom is real man''' print(re.findall(r'^Tom',str1,flags=re.M)) # ['Tom', 'Tom', 'Tom', 'Tom']
print(re.findall(r'\ATom',str1,flags=re.M)) # ['Tom']
print(re.findall(r'man$',str1,flags=re.M)) # ['man', 'man', 'man', 'man']
print(re.findall(r'man\Z',str1,flags=re.M)) # ['man']

代码演示\b和\B:

 # \b相当于^ 和 $ 两者的作用
print(re.search(r'\bne','never')) # <_sre.SRE_Match object; span=(0, 2), match='ne'>
print(re.search(r'ne\b','never')) # None
print(re.search(r'er\b','neverer')) # <_sre.SRE_Match object; span=(5, 7), match='er'> # \B相当于除去边界之后再匹配
print(re.search(r'er\B','never')) # None
print(re.search(r'er\B','nerver')) # <_sre.SRE_Match object; span=(1, 3), match='er'>
print(re.search(r'er\B','ernerver'))# <_sre.SRE_Match object; span=(0, 2), match='er'>
print(re.search(r'\Ber','ernever')) # <_sre.SRE_Match object; span=(5, 7), match='er'>

匹配多个字符:

以下的一些x,y,n等都是变量名

1).模糊匹配:

x?:表示0个或者1个     取值范围:[0,1]

x+:表示1个或者多个    取值范围:[1,无穷大)

x*:表示0个或者多个    取值范围:[0,无穷大)

【注意事项】:关于以上模糊匹配涉及到的三个符号(?、+、*)都满足了正则中的贪婪匹配的行为/特点(在确保匹配的前提下,尽可能多的返回数据),如何取消贪婪行为?即最终希望得到的是,在确保匹配的前提下,尽可能少的返回数据,代码体现:只需在(?、+、*)3个符号后面都显示的再追加一个?即可。具体理解看代码示例:

 import re

 str1 = "aaaaaaacccaaaaaaaaa"

 print(re.search(r'a?',str1).group())    # a
print(re.search(r'a??',str1).group()) #
print(re.search(r'a+',str1).group()) # aaaaaaa
print(re.search(r'a+?',str1).group()) # a
print(re.search(r'a*',str1).group()) # aaaaaaa
print(re.search(r'a*?',str1).group()) # print(re.findall(r'a?',str1)) # ['a', 'a', 'a', 'a', 'a', 'a', 'a', '', '', '', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', '']
print(re.findall(r'a??',str1)) # ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
print(re.findall(r'a+',str1)) # ['aaaaaaa', 'aaaaaaaaa']
print(re.findall(r'a+?',str1)) # ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']
print(re.findall(r'a*',str1)) # ['aaaaaaa', '', '', '', 'aaaaaaaaa', '']
print(re.findall(r'a*?',str1)) # ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''] # 需求:尝试匹配的内容为:Tom ... man
str2 = ' Tom is a good man Tom is a nice man Tom is a super man'
regex1= r' Tom.*man' # 有贪婪行为,中间尽可能一次性多的返回数据
regex2 = r' Tom.*?man' # 取消其贪婪行为,每个数据里面数据尽可能少 lt1 = re.findall(regex1,str2)
print(lt1) # [' Tom is a good man Tom is a nice man Tom is a super man'] lt2 = re.findall(regex2,str2)
30 print(lt2) # [' Tom is a good man', ' Tom is a nice man', ' Tom is a super man']

2).精确匹配:

n{x}:   将n匹配x次

n{x,}:  将n最少有x次,最多无穷大       范围:[x,无穷多)

n{x,y}:将n最少有x次,最多有y次        范围:[x,y]

 str3 = 'aaaaaaacccaaaaaaaaaa'

 print(re.findall(r'a{5}',str3))       # ['aaaaa', 'aaaaa', 'aaaaa']
print(re.findall(r'a{3}',str3)) # ['aaa', 'aaa', 'aaa', 'aaa', 'aaa']
print(re.findall(r'a{3,}',str3)) # 会尽可能返回多的 ['aaaaaaa', 'aaaaaaaaaa']
print(re.findall(r'a{3,}?',str3)) # 取消贪婪行为 ['aaa', 'aaa', 'aaa', 'aaa', 'aaa']
print(re.findall(r'a{8,10}',str3)) # ['aaaaaaaaaa']
print(re.findall(r'a{8,10}?',str3)) # ['aaaaaaaa']

代码演示正则验证qq号:

 # 要求:1).只能都是数字字符;2).不能以0开头;3).长度5~12位

 import re

 qq = ''
regex = r'^[1-9]\d{4,11}$'
pat = re.compile(regex)
res = pat.match(qq)
if res:
print('qq号码合法,号码为:%s' %qq)
else:
print('qq号码不合法...')

正则中的分组:

分组在正则中的定义体现就是使用(...)进行分组操作,理解为分了一个子组。

好处:1).如果正则表达式的逻辑比较复杂,意味着代码比较多,如果在适当的部分进行分组就可以提高代码的阅读性(更有层次感);2).一旦进行了分组操作,在正则表达式的后半部分内容中很多可能需要使用到子组中的内容; 一旦引用了子组,那么这两部分的内容(值)就可以保持一致了。

具体了解看代码演示:

 phone = ''
regex = r'^[1-9]\d{3}(\d)\1{3}$' print(re.match(regex,phone)) # <_sre.SRE_Match object; span=(0, 8), match='62586666'>
print(re.match(regex,phone).groups()) # 返回子组的内容 ('6',)
print(re.match(regex,phone).group()) # 和下面效果一样 62586666
print(re.match(regex,phone).group(0)) # 和上面效果一样 62586666
print(re.match(regex,phone).group(1)) # 第一个子组里面的元祖元素 6 print(re.match(regex,phone).group(2)) # 出现了下标越界的异常:IndexError,原因是正则中只有1个子组,没有index=2这一组 phone = '021-53437788'
regex = r'^(?P<one>\d{3})(?P<two>-)(?P<three>\d{8})$' mo = re.search(regex,phone)
print(mo.groups()) #('021', '-', '53437788')
print(mo.group()) #021-53437788
print(mo.group(0)) #021-53437788
print(mo.group(1)) #
print(mo.group(2)) #-
print(mo.group(3)) #
print(mo.group('one')) #
print(mo.group('two')) #-
print(mo.group('three')) #