Python中的模式匹配与正则表达式的基本用法

目录

用Python创建正则表达式对象

匹配Regex对象

利用括号分组

用管道匹配多个分组

用问号实现可选匹配

用星号匹配零次或者多次

用加号匹配一次或多次

用花括号匹配特定次数

贪心和非贪心匹配

findall()方法

字符分类

建立自定义的字符分类

插入字符和美元字符

通配字符

用点星(.*)匹配所有字符

用句点字符匹配换行符


一般我们如果希望在字符串中查找电话号码,例如这种格式:415-555-4242。我们可以用一个名为isPhoneNumber()的函数来检查字符串是否匹配模式,代码如下:

def isPhoneNumber(text):
    if len(text) != 12:
        return False
    for i in range(0,3):
        if not text[i].isdecimal():
            return False
    if text[3] != '-':
        return False
    for i in range(4,7):
        if not text[i].isdecimal():
            return False
    if text[7] != '-':
        return False
    for i in range(8,12):
        if not text[i].isdecimal():
            return False
    return true

这种方式的局限性是如果想在更长的字符串中寻找电话号码,就必须添加更多代码来寻找电话号码模式。所以我们可以使用更快的方式:正则表达式

正则表达式简称为“regex”,是文本模式的描述方法。例如:\d是一个正则表达式,表示一位数字字符,即任何一位0~9的数字。上面例子中isPhoneNumber()函数匹配转换成正则表达式为\d\d\d-\d\d\d-\d\d\d\d,可以简写成:\d{3}-\d{3}-\d{4}。在一个模式后加上花括号包围的3({3}),表示匹配这个模式3次。

用Python创建正则表达式对象

Python中所有的正则表达式函数都在re模块中,导入改模块:

import re

向re.compile()传入一个字符串值,表示正则表达式,它将返回一个Regex模式对象,创建一个Regex对象匹配电话号码模式,代码如下:

phoneNumRegex = re.compile(r'\d{3}-\d{3}-\d{4}')

现在phoneNumRegex变量包含一个Regex对象。在字符串开始的引号前加上r,使它成为原始字符串,作用就是完全忽略所有的转义字符,可以输出字符串中所有的反斜杠。

匹配Regex对象

Regex对象的search()方法查找传入的字符串,以寻找该正则表达式的所有匹配,如果字符串中没有找到该正则表达式模式,search()方法返回None。如果找到了该模式,search()方法将返回一个Match对象。Match对象有一个group()方法,它返回被查找字符串中实际匹配的文本。例如:

phoneNumRegex = re.compile(r'\d{3}-\d{3}-\d{4}')
mo = phoneNumRegex.search('My number is 415-555-4242')
print('Phone number found: ' + mo.group())

结果为:

Phone number found: 415-555-4242

这里我们将期待的模式传递给re.complie(),并将得到的Regex对象保存在phoneNumgex中,然后在phoneNumgex上调用search(),向它传入想要查找的字符串,查找的结果保存在mo中。这个例子我们知道模式会在这个字符串中找到,所以回返回一个Match对象,直接调用group(),返回匹配结果。

总结一下python中使用正则表达式的几个步骤

1. 用import re导入正则表达式模块

2. 用re.complie()函数创建一个Regex对象(注意使用原始字符串,在字符串前加上r)

3. 向Regex对象的search()方法传入想要查找的字符串,它返回一个Match对象

4. 调用Match对象的group()方法返回实际匹配文本的字符串

利用括号分组

添加括号将在正则表达式中创建“分组”, 正则表达式字符串中的第一对括号是第1组,第二对括号是第2组,例如:(\d\d\d)-(\d\d\d-\d\d\d\d),然后使用group()匹配对象方法传入整数1或2,就可以取得匹配文本的不同部分,向group()方法传入0或者不传入参数,将返回整个匹配的文本

phoneNumRegex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d\d)')
mo = phoneNumRegex.search('My number is 415-555-4242')
mo.group(1) # '415'
mo.group(2) # '555-4242'
mo.group(0) # '415-555-4242'
mo.group() # '415-555-4242'

如果想要一次获取所有的分组,使用groups()方法

mo.groups() # ('415','555-4242')
areaCode,mainNumber = mo.groups()
print(areaCode) # 415
print(mainNumber) # 555-4242

括号在正则表达式中有特殊的含义,如果需要在文本中匹配括号,就需要用反斜杠对括号进行字符转义:

phoneNumberRegex = re.compile(r'(\(\d\d\d\)) (\d\d\d-\d\d\d\d)')
mo = phoneNumRegex.search('My number is (415)-555-4242')
mo.group(1) # '(415)'
mo.group(2) # '555-4242'

在正则表达式中,以下字符具有特殊含义:

.    ^     $     *     +    ?      {       }         [         ]      \         |        (          )

如果需要检查包含这些字符的文本模式,就需要用反斜杠对它们进行转义:

\.    \^     \$     \*     \+    \?      \{       \}         \[         \]      \\         \|        \(          \)

用管道匹配多个分组

字符|称为“管道”,如果想匹配许多表达式中的一个时就能使用它,例如,正则表达式r'Batman|Tina Fey'将匹配'Batman'或'Tina Fey'。如果Batman和Tina Fey都出现在被查找的字符串中,那么第一次出现的匹配文本将作为Match对象返回:

heroRegex = re.compile(r'Batman|Tina Fey')
mo1 = heroRegex.search('Batman and Tina Fey')
mo1.group() # 'Batman'
mo2 = heroRegex.search('Tina Fey and Batman')
mo2.group() # 'Tina Fey'

也可以使用管道来匹配多个模式中的一个,这些模式可以作为正则表达式的一部分。例如,如果希望匹配'Batman'、'Batmobile'、'Batcopter'和'Batbat'中得到任意一个,这些字符串都是以Bat开始,可以通过括号实现:

batRegex = re.compile(r'Bat(man|mobile|copter|bat)')
mo = batRegex.search('Batmobile lost a wheel')
mo.group() # 'Batmobile'
mo.group(1) # 'mobile'
mo.group()返回了完全匹配的文本'Batmobile',mo.group(1)只是返回第一个括号分组内匹配的文本'mobile'

用问号实现可选匹配

有时候想匹配的模式是可选的,不管这段文本在不在,正则表达式都会认为匹配。字符?表明它前面的分组在这个模式中是可选的:

batRegex = re.compile(r'Bat(wo)?man')
mo1 = batRegex.search('The Adventures of Batman')
mo1.group() # 'Batman'
mo2 = batRegex.search('The Adventures of Batwoman')
mo2.group() # 'Batwoman'

 正则表达式中的(wo)?部分表明wo是可选的分组,在该正则表达式匹配的文本中wo将出现零次或一次。

用星号匹配零次或者多次

*星号意味着“匹配零次或多次”,即星号之前的分组可以在文本中出现任意次:

batRegex = re.compile(r'Bat(wo)*man')
mo1 = batRegex.search('The Adventures of Batman')
mo1.group() # 'Batman'
mo2 = batRegex.search('The Adventures of Batwoman')
mo2.group() # 'Batwoman'
mo3 = batRegex.search('The Adenturns of Batwowowowoman')
mo3.group() # 'Batwowowowoman'

如果需要匹配真正的星号字符,就在正则表达式的星号字符前加上反斜杠,即\*。

用加号匹配一次或多次

*意味着匹配零次或者多次,+(加号)则意味着匹配一次或多次。星号不要求分组出现在匹配字符串中,但加号不同,加号前面的分组必须至少出现一次。

batRegex = re.compile(r'Bat(wo)+man')
mo1 = batRegex.search('The Adventures of Batwoman')
mo1.group() # 'Batwoman'

mo2 = batRegex.search('The Adventures of Batwowowowoman')
mo2.group() # 'Batwowowowowman'

mo3 = batRegex.search('The Adventures of Batman')
mo3 == None # True

通过以上例子可以看出,正则表达式Bat(wo)+man不会匹配字符串'The Adventures of Batman',因为加号要求wo至少出现一次。 

用花括号匹配特定次数

如果想要一个分组重复特定次数,就在正则表达式该分组的后面跟上花括号包围的数字。例如,正则表达式(ha){3}将匹配字符串'hahaha',但不会匹配'haha',因为后者只重复了(ha)分组两次。

除了一个数字,还可以指定一个范围,即在花括号中写下最小值、一个逗号和一个最大值。例如,正则表达式(ha){3,5}将匹配'hahaha'、'hahahaha'、'hahahahaha'。

也可以不写花括号中第一个或第二和数字,表示不限定最小值或最大值。例如,(ha){3,}将匹配3次或更多次实例,(ha){,5}将匹配0~5。以下几个正则表达式匹配同样的模式:

(ha){3}匹配---->'(ha)(ha)(ha)'

(ha){3,5}匹配---->'((ha)(ha)(ha)|(ha)(ha)(ha)(ha)|(ha)(ha)(ha)(ha)(ha))'

贪心和非贪心匹配

在字符串'hahahahaha'中,(ha){3,5}可以匹配3个,4个或5个实例,但若用(ha){3,5}去查找'hahahahaha'时,Match对象的group()调用指挥返回'hahahahaha',而不会返回更短的可能结果。毕竟'hahaha'和'hahahaha'也能有效地匹配正则表达式(ha){3,5}。

Python的正则表达式默认是“贪心”的,这表示在有二义的情况下,它们会尽可能匹配最长的字符串。花括号的“非贪心”也称为“惰性”版本尽可能匹配最短的字符串,即在结果的花括号后跟着一个问号。

greedyhaRegex = re.compile(r'(ha){3,5}')
mo1 = greedyhaRegex.search('hahahahaha')
mo1.group() # 'hahahahaha'

nongreedyhaRegex = re.compile(r'(ha){3,5}?')
mo2 = nongreedyhaRegex.search('hahahahaha')
mo2.group() # 'hahaha'

注意:问号在正则表达式中有两种含义:声明非贪心匹配或表示可选的分组 

findall()方法

除了search()方法,Regex对象还有一个findall()方法。search()方法返回一个Match对象,包含被查找字符串中的“第一次”匹配的文本;而findall()方法将返回一组字符串,包含被查找字符串的所有匹配文本。

phoneNumRegex - re.compile(r'\d\d\d-\d\d\d-\d\d\d')
mo = phoneNumRegex.search('cell: 415-555-9999 work: 212-5555-0000')
mo.group() # '415-555-9999'

findall()方法不是返回一个Match对象,而是返回一个字符串列表,条件是在正则表达式中没有分组,代码如下:

phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
phoneNumRegex.findall('cell: 415-555-9999 work: 212-555-0000') # ['415-555-9999', '212-555-0000']

如果在正则表达式中有分组,findall()方法将返回元组的列表。每个元素表示一个找到的匹配,其中的项就是正则表达式中每个分组的匹配字符串。代码如下:

phoneNumRegex = re.compile(r'(\d\d\d)-(\d\d\d)-(\d\d\d\d)')
phoneNumRegex.findall('cell: 415-555-9999 work: 212-555-0000') # [('415','555','9999'),('212','555','0000')]

 总结:作为findall()方法的返回结果有以下两点:

  • 如果在一个没有分组的正则表达式上调用,例如\d\d\d-\d\d\d-\d\d\d\d,findall()方法返回一个匹配字符串列表,如['415-555-9999', '212-555-0000']
  • 如果在一个有分组的正则表达式上调用,例如(\d\d\d)-(\d\d\d)-(\d\d\d\d),findall()方法将返回一个字符串的元组的列表(每个分组对应一个字符串)例如:[('415','555','9999'),('212','555','0000')]

字符分类

我们已经知道\d可以代表任何数字,也就是说\d是正则表达式(0|1|2|3|4|5|6|7|8|9)的缩写。常用的字符分类缩写:

  • \d:0~9的任何数字
  • \D:除0~9的数字以外的任何字符
  • \w:任何字母、数字或下划线字符(可以认为是匹配“单词”字符)
  • \W:除字母、数字和下划线以外的人格字符
  • \s:空格、制表符或换行符(可以认为是匹配“空白”字符)
  • \S:除空格、制表符和换行符以外的任何字符

只匹配字母:[a-zA-Z]

xmasRegex = re.compile(r'\d+\s\w+')
xmasRegex.findall('12 drummers, 11 pipers, 10 lords, 9 ladies')
# ['12 drummers','11 pipers', '10 lords', '9 ladies']

建立自定义字符分类

有时候想匹配一组字符,但缩写的字符分类(\d、\w、\s等)太宽泛,这时可以用方括号定义自己的字符分类。例如,字符分类[aeiouAEIOU]将匹配所有元音字符,且不区分大小写。

在方括号内,普通的正则表达式符号不会被解释,也就意味着不需要在前面加上反斜杠转义、*、?或()字符。在字符分类的左方括号后加上一个插入字符(^),就可以得到“非字符类”,非字符类将匹配不在这个字符类中的所有字符。

liziRegex = re.compile(r'[^ans]')
liziRegex.findall('This is apple') # ['T','h','i',' ','i',' ','p','p','l','e']

插入字符和美元字符

可以在正则表达式的开始处使用插入符号(^),表明匹配必须发生在被查找文本开始处。类似,可以在正则表达式的末尾加上美元符号($),表示该字符串必须以这个正则表达式的模式结束。可以同时使用^和$,表示整个字符串必须匹配该模式。例如,正则表达式r'^Hello'匹配以'Hello'开始的字符串,代码如下:

beginsWithHello = re.compile(r'^Hello')
beginsWithHello.search('Hello world')
# <re. Match object; span=(0, 5), match='Hello'>

正则表达式r'\d$'匹配以数字0~9结束的字符串。

正则表达式r'^\d+$'匹配从开始到结束都是数字的字符串。 

通配字符

在正则表达式中,.(句点)字符成为“通配字符”,它匹配换行符之外的所有字符。例如:

atRegex = re.compile(r'.at')
atRegex.findall('The cat in the hat sat on the flat mat.')
# ['cat','hat','sat','lat','mat']

注意:句点 字符只匹配一个字符。如果要匹配真正的句点,就使用反斜杠转义,即\.

用点星(.*)匹配所有字符

有时候想要匹配所有字符串。句点表示“换行符外的所有单个字符”,星号字符表示“前面字符出现零次或多次”。例如:

nameRegex = re.compile(r'First Name: (.*) Last Name: (.*)')
mo = nameRegex.search('First Name: Al Last Name: Sweigart')
mo.group(1) # 'Al'
mo.group(2) # 'Sweigart'

点-星使用“贪心”模式:它总是匹配尽可能多的文本。要用“非贪心”模式匹配所有文本,就使用点-星和问号,像和花括号使用时已有,问号告诉python使用非贪心模式匹配。 

用句点字符匹配换行符

点-星将匹配换行符外的所有字符。传入re.DOTALL作为re.compile()的第二个参数,可以让句点字符匹配所有字符,包括换行符。

newlineRegex = re.compile('.*',re.DOTALL)
newlineRegex.search('Serve the public trust.\nProtect the innocent.').group()
# 'Serve the public trust.\nProtect the innocent.'

用sub()方法替换字符串

正则表达式不仅能找到文本模式,还能用新的文本替换掉这些模式。Regex对象的sub()方法需要传入两个参数,第一个参数是一个字符串,用于替换发现的匹配,第二个参数是一个字符串,即正则表达式。sub()方法返回替换后的字符串,例如:

nameRegex = re.compile(r'Agent \w+')
nameRegex.sub('XXX','Agent Alice gave the secret documents to Agent Bob.')
# XXX gave the secret documents to XXX.

总结

  • ?匹配零次或一次前面的分组
  • *匹配零次或多次前面的分组
  • +匹配一次或多次前面的分组
  • {n}匹配n次前面的分组
  • {n,}匹配n次或更多次前面的分组
  • {,m}匹配零次到m次前面的分组
  • {n,m}匹配至少n次,至多m次前面的分组
  • {n,m}?、*?或+?对前面的分组进行非贪心匹配
  • ^spam意味着字符串必须以spam开始
  • spam$意味着字符串必须以spam结束
  • .匹配所有字符,换行符除外
  • \d、\w和\s分别匹配数字、单词和空格
  • \D、\W和\S分别匹配数字、单词和空格外的所有字符
  • [abc]匹配方括号内的任意字符(如a、b或c)
  • [^abc]匹配不在方括号内的任意字符

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值