目录

  一. 正则表达式

  二. 特殊的元字符

  三. python3的re模块方法

  四. python3的re模块练习

  五. 第一章课后练习题

  六. re模块综合应用之计算器

一. 正则表达式

  正则表达式是由一堆字符和特殊符号组成的字符串。它可以为我们提供高级的文本搜索,匹配,替换功能。当然,正则表达式也不是python独有的一种模式,而是凌驾于语言之上的一种跨平台的通用标准。当我们学会了正则表达式之后,将会能够更加容易的处理我们的文本和数据。让我们开始正则之旅吧。

二. 特殊的元字符

  正则表达式本质上就是一堆字符串,只不过构成这个字符串的每一个字符都是有特别意义的,我们想要去真正的去了解正则表达式,就必须要清楚的记得特殊字符的含义。下图是python核心编程中关于元字符的描述。虽然有很多,但是你一定要背会(当然在学的过程中你会发现其实也没有那么难背,用着用着就记住了,但是还是提醒,不管是什么方法,一定要记住)。

  \"\"

  \"\"

   在我们真正开始正则表达式之前,我们首先要了解一个工具,那就是python的re模块,快速的了解,只需要知道通过这个模块我们可以查看写出来的正则表达式是否准确就可以了。之后我们会再去详细的查看,使用方法如下:

>>> re.search('^s.*$', 'sllsljegleg')# 第一个参数:就是我们写出来的正则表达式(从表面上看就是字符串)第二个参数:就是我们匹配的字符串,如果匹配到了就返回值
<_sre.SRE_Match  ; span=(0, 11), match='sllsljegleg'>  # 最后match后面的就是我们匹配到的字符串
>>> re.search('^s.*$', 'llsljegleg') # 如果没有匹配到就没有显示
>>>

第一类: 位置匹配

  位置匹配就是专门用来描述位置的元字符,有四个: 【^,$, \\A,\\Z】(注意是有大小写之分的),^ 和\\A都表示字符串的开头,$ 和\\Z都表示字符串的结尾,为什么会有两个元字符去表示同一个事物呢?这是因为在一些国际键盘上是没有脱字符的,所以设计的时候又设计了\\A和\\Z来表示。

>>> re.search('^From','From to China')  # 以From开头的字符串
<_sre.SRE_Match ; span=(0, 4), match='From'>


>>> re.search('/bin/tcsh$', 'python /bin/tcsh') # 以/bin/tcsh结尾的字符串  
<_sre.SRE_Match ; span=(7, 16), match='/bin/tcsh'>

>>> re.search('^Subject:hi$', 'Subject:hi')  # 如果前面有脱字符,后面美元符,就代表只匹配里面的值,此例中就是只匹配Subject:hi
<_sre.SRE_Match ; span=(0, 10), match='Subject:hi'>
>>>

  需要匹配【$】和脱字符【^】的时候怎么做呢,通过【\\】斜杠进行转义

>>> re.search('\\$', '$')   通过\\转义就可以匹配到$ 符了
<_sre.SRE_Match  ; span=(0, 1), match='$'>
>>> re.search('\\^\\$', 'hello ^$')
<_sre.SRE_Match  ; span=(6, 8), match='^$'>
>>>

  【\\A, \\Z】的用法和^$的用法是一样的

\"\"\"\"
>>> re.search('\\AFrom','From to China')  # 以From开头的字符串
<_sre.SRE_Match  ; span=(0, 4), match='From'>


>>> re.search('/bin/tcsh\\Z', 'python /bin/tcsh') # 以/bin/tcsh结尾的字符串  
<_sre.SRE_Match  ; span=(7, 16), match='/bin/tcsh'>

>>> re.search('\\ASubject:hi\\Z', 'Subject:hi')  # 如果前面有脱字符,后面美元符,就代表只匹配里面的值,此例中就是只匹配Subject:hi
<_sre.SRE_Match  ; span=(0, 10), match='Subject:hi'>
>>>
\\A和\\Z的使用方法

  【\\b】匹配任何单词边界

>>> s = 'this island is beautiful'
>>> import re
>>> re.search(r'\\bis', s)    # 此时我们会发现匹配到了索引为5,7的字符,也就是说island这个前面的is匹配到了
<_sre.SRE_Match  ; span=(5, 7), match='is'>
>>> re.search(r'\\bis\\b', s)  # 如果加上\\b界定上单词边界之后就会之匹匹厄后面的is了,因为前面的is并不是一个单词
<_sre.SRE_Match  ; span=(12, 14), match='is'>
>>>

第二类: 重复匹配

  重复匹配就是将之前匹配的正则表达式重新匹配多少次。重复匹配是正则表达式中基本上最常用的模式。

  【*】匹配前面正则表达式0次或者多次

>>> re.search('\\d\\d*', '1')  # 第一个\\d匹配一个数字,第二个\\d本来也是匹配一个数字,但是后面加上了一个*,代表前面可以不匹配,也可以匹配一次或者多次
<_sre.SRE_Match  ; span=(0, 1), match='1'>
>>> re.search('\\d\\d*', '12')   # 这个就是第二个\\d匹配了1次
<_sre.SRE_Match  ; span=(0, 2), match='12'>
>>> re.search('\\d\\d*', '123')  # 这个就是第二个\\d匹配了2次
<_sre.SRE_Match  ; span=(0, 3), match='123'>
>>>

  【+】匹配前面正则表达式1次或者多次

>>> re.search('\\d\\d+', '1')   # 第一个\\d匹配到了1,但是第二个\\d后面有+代表最少匹配一次,但是字符串没有数字了,所以就没有匹配到值
>>> re.search('\\d\\d+', '12')  # 这个代表第二个\\d匹配了一次
<_sre.SRE_Match  ; span=(0, 2), match='12'>
>>> re.search('\\d\\d+', '123')  # 这个代表第二个\\d匹配了两次
<_sre.SRE_Match  ; span=(0, 3), match='123'>
>>>

  【?】匹配前面正则表达式0次或者1次

>>> re.search('\\d\\d?', '1')  # 第一个\\d匹配到了1,但是第二个\\d后面有?说明可以匹配0次,也就是不匹配
<_sre.SRE_Match  ; span=(0, 1), match='1'>
>>> re.search('\\d\\d?', '12')  # 第二个\\d匹配了1次
<_sre.SRE_Match  ; span=(0, 2), match='12'>
>>> re.search('\\d\\d?', '123')  # 虽然匹配到了,但是发现匹配到的值还是12,最多只能匹配一次
<_sre.SRE_Match  ; span=(0, 2), match='12'>
>>>

  【{n}】匹配前面正则表达式n次

>>> re.search('\\d\\d{2}', '12')  
>>> re.search('\\d\\d{2}', '1')
>>> re.search('\\d\\d{2}', '123')    # {2}代表前面的\\d必须要匹配两次,所以只能匹配到123
<_sre.SRE_Match  ; span=(0, 3), match='123'>
>>> re.search('\\d\\d{2}', '1234')
<_sre.SRE_Match  ; span=(0, 3), match='123'>
>>>

  【{n, m}】匹配前面正则表达式n到m次

>>> re.search('\\d\\d{2,4}', '12')
>>> re.search('\\d\\d{2,4}', '123')  # {2,4}代表匹配前面\\d2次到4次,因此我们可以发现最少要匹配两次,最多要匹配4次
<_sre.SRE_Match  ; span=(0, 3), match='123'>
>>> re.search('\\d\\d{2,4}', '1234')
<_sre.SRE_Match  ; span=(0, 4), match='1234'>
>>> re.search('\\d\\d{2,4}', '12345')
<_sre.SRE_Match  ; span=(0, 5), match='12345'>
>>> re.search('\\d\\d{2,4}', '123456')
<_sre.SRE_Match  ; span=(0, 5), match='12345'>
>>>

  重复匹配的例子

# 1. 匹配字符d或b之后跟着一个o最后可以跟着一个t也可以不跟着一个t的例子
>>> re.search('[db]ot?', 'do')
<_sre.SRE_Match  ; span=(0, 2), match='do'>
>>> re.search('[db]ot?', 'dot')
<_sre.SRE_Match  ; span=(0, 3), match='dot'>
>>> re.search('[db]ot?', 'bo')
<_sre.SRE_Match  ; span=(0, 2), match='bo'>
>>> re.search('[db]ot?', 'bot')
<_sre.SRE_Match  ; span=(0, 3), match='bot'>
>>>

# 2. 匹配9-16位的信用卡号
>>> re.search('[0-9]{9, 16}', '1234567890')  # 注意不能加空格
>>> re.search('[0-9]{9,16}', '1234567890')
<_sre.SRE_Match  ; span=(0, 10), match='1234567890'>
>>>

# 3. 匹配全部有效的html标签
>>> re.search('</?[^>]+>', '</>')  #/?代表/可以不出现,代表的是开头的标签,也可以出现1次,代表的是结尾的标签,[^>]代表的是除了>的任何字符,后面跟上+,也就是说除了>的任何字符出现一次或者多次
<_sre.SRE_Match  ; span=(0, 3), match='</>'>
>>> re.search('</?[^>]+>', '<hello>')
<_sre.SRE_Match  ; span=(0, 7), match='<hello>'>
>>>

第三类: 范围匹配

  范围匹配是通过一个或者一组特殊的字符代表一个范围的数据。例如:【\\d】代表的是0-9的数字数字。【大写字母的都是与之相反的】

     【.】点代表的是除了换行符之外的任意的字符的匹配

>>> re.search('f.o', 'fao')    # 匹配字母f和o之间的任何一个字符
<_sre.SRE_Match  ; span=(0, 3), match='fao'>
>>> re.search('f.o', 'f#o')
<_sre.SRE_Match  ; span=(0, 3), match='f#o'>
>>> re.search('f.o', 'f9o')
<_sre.SRE_Match  ; span=(0, 3), match='f9o'>
>>> re.search('..', 'js')      # 匹配任意的两个字符
<_sre.SRE_Match  ; span=(0, 2), match='js'>  
>>> re.search('..', 'ss')
<_sre.SRE_Match  ; span=(0, 2), match='ss'>
>>>

  【\\d】表示0-9的任何十进制数字

>>> re.search('\\d', '1')   # 匹配0-9的任何一个字符
<_sre.SRE_Match  ; span=(0, 1), match='1'>
>>> re.search('\\d', '2')
<_sre.SRE_Match  ; span=(0, 1), match='2'>
>>> re.search('\\d', 'z')   # z不是0-9所以匹配不到
>>>

  【\\w】字母数字下划线 相当于[a-zA-0-9_]的缩写

>>> re.search('\\w', '1')  # 字母数字下滑先都可以匹配到,而且也只是匹配一个字符
<_sre.SRE_Match  ; span=(0, 1), match='1'>
>>> re.search('\\w', 'a')
<_sre.SRE_Match  ; span=(0, 1), match='a'>
>>> re.search('\\w', '_')
<_sre.SRE_Match  ; span=(0, 1), match='_'>
>>> re.search('\\w', '#')  # 因为#不在范围之内,所以没有匹配到
>>>

  \\d和\\w的一些例子

#1.   一个数字字母组成的字符串和一串由数字组成的字符串
>>> re.search('\\w+-\\d+', 'ab123sejg-123456')   # \\w代表数字字母,+代表前面的数字字母可以出现一次或者多次,也就是把字母数字组成的字符串表示好了,\\d+也是一样的效果,代表的是一串由数字组成的字符串
<_sre.SRE_Match  ; span=(0, 16), match='ab123sejg-123456'>
>>>

#2. 以字母开头,其余字符是字母或者数字
 >>> re.search('^[A-Za-z]\\w*', 'a123lsege')
<_sre.SRE_Match  ; span=(0, 9), match='a123lsege'>
>>>

#3. 美国电话号码的格式 800-555-1212
>>>
>>> re.search('\\d{3}-\\d{3}-\\d{4}', '800-555-1212') # \\d代表的是一个数字{3}代表的是前面的数字出现三次,也就是把800给匹配出来了,然后就是单纯的一个-,后面的也是一个效果,组合起来就把电话号码给匹配出来了。
<_sre.SRE_Match  ; span=(0, 11), match='800-555-1212'>
>>>

# 4. 电子邮件地址xxx@yyy.com
>>> re.search('\\w+@\\w+\\.com', 'hwt@qq.com') # 首先\\w+匹配一个数字字母下划线的字符串,然后一个单纯的@,然后又是一个字符串, 之后\\.因为做了转义,代表的就是一个单纯的.
<_sre.SRE_Match  ; span=(0, 10), match='hwt@qq.com'>
>>>

  【\\s】匹配空格字符

>>> re.search('of\\sthe', 'of the')  # 就是匹配一个空格字符, 无论是换行符,\\t\\v\\f都是可以匹配到的
<_sre.SRE_Match  ; span=(0, 6), match='of the'>
>>>

  【[a-b]】中括号代表a-b的一个范围,代表的是从a-b的字符之间匹配一个字符

  应用一:创建字符串

>>> re.search('[A-Z]', 'D')  # 匹配A-Z之间的一个字符,所以D就可以匹配了,之间不能有空格
<_sre.SRE_Match  ; span=(0, 1), match='D'>
>>>
>>> re.search('[ab][cd]', 'ac') # 从第一个方框中取出一个值与第二个方框中取出一个值进行组合,注意不能匹配到ab和cd,如果想匹配ab和cd需要通过择一匹配符号也就是[|]
<_sre.SRE_Match  ; span=(0, 2), match='ac'>
>>> re.search('[ab][cd]', 'ad')
<_sre.SRE_Match  ; span=(0, 2), match='ad'>
>>> re.search('[ab][cd]', 'ac')
<_sre.SRE_Match  ; span=(0, 2), match='ac'>
>>> re.search('[ab][cd]', 'ad')
<_sre.SRE_Match  ; span=(0, 2), match='ad'>
>>>

  【[^ a -b]】匹配除了a-b之外的任何一个字符

# 1. z后面跟着任何一个字符然后再跟着一个0-9之间的数字
>>> re.search('z.[0-9]', 'z#0')
<_sre.SRE_Match  ; span=(0, 3), match='z#0'>
>>> re.search('z.[0-9]', 'z#1')
<_sre.SRE_Match  ; span=(0, 3), match='z#1'>
>>>

# 2. 除了aeiou之外的任何字符
>>> re.search('[^aeiou]', 's')
<_sre.SRE_Match  ; span=(0, 1), match='s'>
>>>

# 3. 除了制表符或者换行符之外的任何一个字符
>>> re.search('[^\\n\\t]', 'aljg')
<_sre.SRE_Match  ; span=(0, 1), match='a'>
>>>

第四类: 分组匹配

  分组匹配严格意义上来讲并不是一个匹配的元字符,因为它本身并不会影响匹配的结果,只是会把匹配的结果按照括号分开,然后存储到一定的位置,便于我们之后使用的。那为什么要有分组呢?因为在很多的时候我们并不是对于匹配出来的字符感兴趣的,有时候我们只是对于匹配字符的某一个块感兴趣,可能还会对这一块进行一系列的操作。这就需要分组来帮我们做这件事了。

  分组相对来说较为简单,但是却相当重要,简单在于它并不会影响我们的匹配结果,重要在数据匹配之后大部分都要用到这个分组。

  【 () 】,在之后介绍group和groups的时候会提到

>>> import re
>>> re.search('(\\d+)([a-z])(\\.)(\\w+)', '123c.sleg234')  # 这个和下面匹配的结果是一样的,意思是加上括号分组的时候并不会对匹配的结果产生影响
<_sre.SRE_Match  ; span=(0, 12), match='123c.sleg234'>  # 它只是通过分组给我们返回了一系列的数据,我们可以通过group(s)方法获得而已
>>> re.search('\\d+[a-z]\\.\\w+', '123c.sleg234')         
<_sre.SRE_Match  ; span=(0, 12), match='123c.sleg234'>
>>>

第五类:择一匹配符

  【|】竖杠代表的是从几个正则表达式中得到一个

>>> re.search('ab|cd', 'ab')  # 从左边的ab和cd中匹配相应的数据,但是不会匹配ac,这也是和[]的区别
<_sre.SRE_Match  ; span=(0, 2), match='ab'>
>>> re.search('ab|cd', 'cd')
<_sre.SRE_Match  ; span=(0, 2), match='cd'>
>>> re.search('ab|cd', 'ac')
>>>

第六类:扩展表示法

  扩展表示法等之后真正用到了再回来看吧

  \"\"

三. python3re模块方法

方法一: compile编译

  程序是我们写出来的一堆字符串,计算机是看不懂的,因此想要让我们写出来的代码可以正常的计算机中执行,就必须将代码块编译成字节码(也就是程序能够理解的语言)。而complie编译就是这个原理,也就是我提前将字符串编译成一个对象,之后你要进行使用的时候不必再进行编译了,直接调用此对象就可以了。对于只调用一次的正则表达式用不用此方法都是可以的,但是对于那些需要调用多次的正则表达式而言,提前编译就能够大大的提升执行性能。complie方法就是为了做这样的一件事的。

>>> import re
>>> s = '\\d+'     # 这个是自己写的正则表达式
>>> int_obj = re.compile(s)   # 通过complie方法将s编译成对象,之后就直接可以通过这个对象直接去调用方法,可以节省性能 
>>> print(int_obj.search('123'))
<_sre.SRE_Match  ; span=(0, 3), match='123'>
>>> print(int_obj.search('123'))
<_sre.SRE_Match  ; span=(0, 3), match='123'>
>>> print(int_obj.search('123'))
<_sre.SRE_Match  ; span=(0, 3), match='123'>
>>> print(int_obj.search('123'))
<_sre.SRE_Match  ; span=(0, 3), match='123'>
>>>

方法二:group 和 groups方法

  (1).group和groups方法都是在正则表达式匹配成功之后调用的方法,如果没有匹配成功调用此方法会报错

  (2).group方法会得到一个如下的列表【当前匹配的字符串, 分组一,分组二........】没有分组就只有一个元素

  (3).groups方法会得到一个如下的元组 ( 分组一,分组二......), 没有分组就是空空列表

  (4).这两个方法只能是search和match成功匹配到的对象才可以使用。

以美国电话号码匹配为例s1 = '\\d{3}-\\d{3}-\\d{4}'

(1).没有分组的情况下

>>> s1 = '\\d{3}-\\d{3}-\\d{4}'  # 美国电话号码的格式匹配正则表达式
>>> s2 = '(\\d{3})-(\\d{3})-(\\d{4})'    # 将美国的电话号码进行分组
>>> tel = '099-898-2392'  # 这是一个电话格式
>>> re.search(s1, tel)     # 之前没有学group的时候我们一直得到的都是下面的这种对象
<_sre.SRE_Match  ; span=(0, 12), match='099-898-2392'>
>>> re.search(s1, tel).group()   # 得到匹配的值    
'099-898-2392'
>>> re.search(s1, tel).group(0)  # 和上面的是一样的,因为没有分组得到的列表中就只有一个元素
'099-898-2392'
>>> re.search(s1, tel).group(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: no such group
>>> re.search(s1, tel).groups()   # 没有分组的情况下groups就是空的元组
()
>>>

(2)有分组的情况下

>>> s1 = '\\d{3}-\\d{3}-\\d{4}'  # 美国电话号码的格式匹配正则表达式
>>> s2 = '(\\d{3})-(\\d{3})-(\\d{4})'    # 将美国的电话号码进行分组
>>> tel = '099-898-2392'
>>>
>>> print(re.search(s2, tel).group())
099-898-2392
>>> print(re.search(s2, tel).group(0))  # 获得的是整个匹配的对象
099-898-2392
>>> print(re.search(s2, tel).group(1))  # 第一个子组099
099
>>> print(re.search(s2, tel).group(2))  # 获得的第二个子组898
898
>>> print(re.search(s2, tel).group(3))  # 获得的第三个子组2392
2392
>>> print(re.search(s2, tel).groups())  # 获得子组的一个列表
('099', '898', '2392')
>>>

方法三:  match,search, findall

  match: 在字符串的开头对写的正则表达式进行匹配。 匹配成功,返回匹配对象,匹配失败,返回None

  search:在整个字符串中对写的正则表达式进行匹配。 匹配成功,返回第一个被匹配的对象,匹配失败,返回None

  findall: 在整个字符串中对写的正则表达式进行匹配。只要是匹配成功的就添加到列表中,最后返回一个列表

match:

>>> re.match('foo', 'food on the table').group()  # 从字符串开头开始匹配,匹配到了foo,所以group得到的是匹配到的foo
'foo'
>>> re.match('ood', 'food on the table')  # 从字符串开头开始匹配,发现并不是00d,所以没有匹配到结果,返回一个空
>>>

search:

>>> re.match('foo', 'seafood')   # 字符串开头并不是foo,所以match没有匹配到
>>> re.search('foo', 'seafood').group()   # search是在整个字符串中匹配的,所以
'foo'
>>>

findall:

>>> re.match('foo', 'seafood, seafood')   # 字符串开头并不是foo,所以没有匹配
>>> re.search('foo', 'seafood, seafood').group()  # 在字符串匹配到第一个foo的时候就不再进行匹配了
'foo'
>>> re.findall('foo', 'seafood, seafood')  # 在字符串给中查找到所有匹配到的字符串放在列表中
['foo', 'foo']
>>> 

方法四: finditer, findall

  finditer和findall作用其实是一样的,不同之处在于finditer返回的是一个迭代器(更加节省内存),而findall返回的是一个列表。

  (1).在没有分组的情况下,每一个被匹配的元素都会作为列表的元素

  (2).在分组的情况下,被匹配的元素会把子组放在一个元组中放在列表中(比较绕,直接上例子)

(1)在没有分组的情况下

s = 'This and that.'
print(re.findall(r'Th\\w+ and th\\w+', s))   # 会把匹配到的信息一个一个的放在列表中,此处只是匹配了一个
print(re.finditer(r'Th\\w+ and th\\w+', s).__next__())  # iter返回一个迭代器,可以通过__next__去获得第一个对象,注意此处是类似于match获得的对象
print(re.match('s', 's'))     # 为了和上面进行对比的
# 结果:
# ['This and that']
# <_sre.SRE_Match  ; span=(0, 13), match='This and that'>
# <_sre.SRE_Match  ; span=(0, 1), match='s'>

(2)有分组的情况

s = 'This and that.'
print(re.findall(r'(Th\\w+) and (th\\w+)', s))   # 有分组就会发现列表中其实放的并不是匹配到的值了,而是子组元组
print(re.finditer(r'(Th\\w+) and (th\\w+)', s).__next__())  #但是iter得到的还是匹配的对象,如果想得到子组可以通过group去获得
print(re.match('s', 's'))     # 为了和上面进行对比的

# 结果:

					
				
收藏 打印
您的足迹: