我心中的王者:Python-第16章 正则表达式(Regular Expression)

我心中的王者:Python-第16章 正则表达式(Regular Expression)

正则表达式(Regular Expression)主要功能是执行模式的比对与搜寻,甚至Word文件也可以使用正则表达式处理搜寻(search)与取代(replace)功能,本章首先会介绍如果没用正则表达式,如何处理搜寻文字功能,再介绍使用正则表达式处理这类问题,读者会发现整个工作变得更简洁容易。

16-1 使用Python硬功夫搜寻文字

如果现在打开手机的联络信息可以看到,台湾手机号码的格式如下:

 0952-282-020  # 可以表示为xxxx-xxx-xxx,每个x代表一个0-9数字

从上述可以发现手机号码格式是由4个数字,1个连字符号,3个数字,1个连字符号,3个数字所组成。

程序实例ch16_1.py:用传统知识设计一个程序,然后判断字符串是否含有台湾的手机号码格式。

# ch16_1.py
def taiwanPhoneNum(string):
    """检查是否有含手机联络信息的台湾手机号码格式"""
    if len(string) != 12:       # 如果长度不是12
        return False            # 传回非手机号码格式
    
    for i in range(0, 4):       # 如果前4个字出现非数字字符
        if string[i].isdecimal() == False:
            return False        # 传回非手机号码格式
        
    if string[4] != '-':        # 如果不是'-'字符
        return False            # 传回非手机号码格式
   
    for i in range(5, 8):       # 如果中间3个字出现非数字字符
        if string[i].isdecimal() == False:
            return False        # 传回非手机号码格

    if string[8] != '-':        # 如果不是'-'字符
        return False            # 传回非手机号码格式

    for i in range(9, 12):      # 如果最后3个字出现非数字字符
        if string[i].isdecimal() == False:
            return False        # 传回非手机号码格
    return True                 # 通过以上测试

print("I love Ming-Chi: 是台湾手机号码", taiwanPhoneNum('I love Ming-Chi'))
print("0932-999-199:    是台湾手机号码", taiwanPhoneNum('0932-999-199'))

执行结果

I love Ming-Chi: 是台湾手机号码 False
0932-999-199:    是台湾手机号码 True

上述程序第4和5行是判断字符串长度是否12,如果不是则表示这不是手机号码格式。程序第7至9行是判断字符串前4码是不是数字,如果不是则表示这不是手机号码格式。程序第11至12行是判断这个字符是不是‘-’,如果不是则表示这不是手机号码格式。程序第14至16行是判断字符串索引[5][6][7]码是不是数字,如果不是则表示这不是手机号码格式。程序第18至19行是判断这个字符是不是‘-’,如果不是则表示这不是手机号码格式。程序第21至23行是判断字符串索引[9][10][11]码是不是数字,如果不是则表示这不是手机号码格式。如果通过了以上所有测试,表示这是手机号码格式,程序第24行传回True。

在真实的环境应用中,我们可能需面临一段文字,这段文字内穿插一些数字,然后我们必须将手机号码从这段文字抽离出来。

程序实例ch16_2.py:将电话号码从一段文字抽离出来。

# ch16_2.py
def taiwanPhoneNum(string):
    """检查是否有含手机联络信息的台湾手机号码格式"""
    if len(string) != 12:       # 如果长度不是12
        return False            # 传回非手机号码格式
    
    for i in range(0, 4):       # 如果前4个字出现非数字字符
        if string[i].isdecimal() == False:
            return False        # 传回非手机号码格式
        
    if string[4] != '-':        # 如果不是'-'字符
        return False            # 传回非手机号码格式
   
    for i in range(5, 8):       # 如果中间3个字出现非数字字符
        if string[i].isdecimal() == False:
            return False        # 传回非手机号码格

    if string[8] != '-':        # 如果不是'-'字符
        return False            # 传回非手机号码格式

    for i in range(9, 12):      # 如果最后3个字出现非数字字符
        if string[i].isdecimal() == False:
            return False        # 传回非手机号码格
    return True                 # 通过以上测试

def parseString(string):
    """解析字符串是否含有电话号码"""
    notFoundSignal = True       # 注记没有找到电话号码为True
    for i in range(len(string)):  # 用循环逐步抽取12个字符做测试
        msg = string[i:i+12]
        if taiwanPhoneNum(msg):
            print("电话号码是: %s" % msg)
            notFoundSignal = False        
    if notFoundSignal:          # 如果没有找到电话号码则打印
        print("%s 字符串不含电话号码" % string)

msg1 = 'Please call my secretary using 0930-919-919 or 0952-001-001'
msg2 = '请明天17:30和我一起参加明志科大教师节晚餐'
msg3 = '请明天17:30和我一起参加明志科大教师节晚餐, 可用0933-080-080联络我'
parseString(msg1)
parseString(msg2)
parseString(msg3)

执行结果

电话号码是: 0930-919-919
电话号码是: 0952-001-001
请明天17:30和我一起参加明志科大教师节晚餐 字符串不含电话号码
电话号码是: 0933-080-080

从上述执行结果可以得知我们成功地从一个字符串分析,然后将电话号码分析出来了。分析方式的重点是程序第26行到35行的parseString函数,这个函数重点是第29至33行,这个循环会逐步抽取字符串的12个字符做比对,将比对字符串放在msg字符串变量内,下列是各循环次序的msg字符串变量内容。
在这里插入图片描述

程序第28行将没有找到电话号码notFoundSignal设为True,如果有找到电话号码程序33行将notFoundSignal标示为False,当parseString( )函数执行完,notFoundSignal仍是True,表示没找到电话号码,所以第35行打印字符串不含电话号码。

上述使用所学的Python硬功夫虽然解决了我们的问题,但是若是将电话号码改成中国大陆手机号(xxx-xxxx-xxxx)、美国手机号(xxx-xxx-xxxx)或是一般公司行号的电话,整个号码格式不一样,要重新设计可能需要一些时间。不过不用担心,接下来笔者将讲解的Python的正则表达式可以轻松解决上述困扰。

16-2 正则表达式的基础

Python有关正则表达式的方法是在re模块内,所以使用正则表达式需要导入re模块。

import re # 导入re模块

16-2-1 建立搜寻字符串模式

在前一节我们使用isdecimal( )方法判断字符是否是0—9的数字。

正则表达式是一种文本模式的表达方法,在这个方法中使用\d表示0—9的数字字符,采用这个观念我们可以将前一节的手机号码xxxx-xxx-xxx改用下列正则表达方式表示:

 '\d\d\d\d-\d\d\d-\d\d\d'

由逸出字符的观念可知,将上述表达式当字符串放入函数内需增加‘\’,所以整个正则表达式的使用方式如下:

 '\\d\\d\\d\\d-\\d\\d\\d-\\d\\d\\d'

在3-4-9小节笔者有介绍字符串前加r可以防止字符串内的逸出字符被转译,所以又可以将上述正则表达式简化为下列格式:

 r'\d\d\d\d-\d\d\d-\d\d\d'

16-2-2 使用re.compile( )建立Regex对象

Regex是Regular expression的简称,在re模块内有compile( )方法,可以将16-2-1节的欲搜寻字符串的正则表达式当作字符串参数放在此方法内,然后会传回一个Regex对象。如下所示:

   phoneRule = re.compile(r'\d\d\d\d-\d\d\d-\d\d\d') # 建立phoneRule对象

16-2-3 搜寻对象

在Regex对象内有search( )方法,可以由Regex对象启用,然后将欲搜寻的字符串放在这个方法内,沿用上述观念程序片段如下:

 phoneNum = phoneRule.search(msg)  # msg是欲搜寻的字符串

如果找不到比对相符的字符串会传回None,如果找到比对相符的字符串会将结果传回所设定的phoneNum变量对象,这个对象在Python中称之为MatchObject对象,将在16-6节完整解说。现在笔者将介绍实用性较高的部分,处理此对象主要是将搜寻结果传回,我们可以用group( )方法将结果传回,不过search( )将只传回第一个比对相符的字符串。

程序实例ch16_3.py:使用正则表达式重新设计ch16_2.py。

# ch16_3.py
import re

msg1 = 'Please call my secretary using 0930-919-919 or 0952-001-001'
msg2 = '请明天17:30和我一起参加明志科大教师节晚餐'
msg3 = '请明天17:30和我一起参加明志科大教师节晚餐, 可用0933-080-080联络我'

def parseString(string):
    """解析字符串是否含有电话号码"""
    phoneRule = re.compile(r'\d\d\d\d-\d\d\d-\d\d\d')
    phoneNum = phoneRule.search(string)
    if phoneNum != None:        # 检查phoneNum内容
        print("电话号码是: %s" % phoneNum.group())
    else:
        print("%s 字符串不含电话号码" % string)

parseString(msg1)
parseString(msg2)
parseString(msg3)

执行结果

电话号码是: 0930-919-919
请明天17:30和我一起参加明志科大教师节晚餐 字符串不含电话号码
电话号码是: 0933-080-080

在程序实例ch16_2.py我们使用了约21行做字符串解析,当我们使用Python的正则表达式时,只用第10和11行共2行就解析了字符串是否含手机号码了,整个程序变得简单许多。不过上述msg1字符串内含2组手机号码,使用search( )只传回第一个发现的号码,下一节将改良此方法。

16-2-4 findall( )

从方法的名字就可以知道,这个方法可以传回所有找到的手机号码。这个方法会将搜寻到的手机号码用列表方式传回,这样就不会有只显示第一个搜寻到的手机号码的缺点,如果没有比对相符的号码就传回[ ]空列表。要使用这个方法的关键指令如下:

  phoneRule = re.compile(r'\d\d\d\d-\d\d\d-\d\d\d') # 建立phoneRule对象
  phoneNum = phoneRule.findall(string)                 # string是欲搜寻的字符串

findall( )函数由phoneRule对象启用,最后会将搜寻结果的列表传给phoneNum,只要打印phoneNum就可以得到执行结果。

程序实例ch16_4.py:使用findall( )搜寻字符串,第10行定义正则表达式,打印结果。

# ch16_4.py
import re

msg1 = 'Please call my secretary using 0930-919-919 or 0952-001-001'
msg2 = '请明天17:30和我一起参加明志科大教师节晚餐'
msg3 = '请明天17:30和我一起参加明志科大教师节晚餐, 可用0933-080-080联络我'

def parseString(string):
    """解析字符串是否含有电话号码"""
    phoneRule = re.compile(r'\d\d\d\d-\d\d\d-\d\d\d')
    phoneNum = phoneRule.findall(string)     # 用列表传回搜寻结果
    print("电话号码是: %s" % phoneNum)       # 列表方式显示电话号码

parseString(msg1)
parseString(msg2)
parseString(msg3)

执行结果

电话号码是: ['0930-919-919', '0952-001-001']
电话号码是: []
电话号码是: ['0933-080-080']

16-2-5 再看re模块

其实Python语言的re模块对于search( )和findall( )有提供更强的功能,可以省略使用re.compile( )直接将比对模式放在各自的参数内,此时语法格式如下:

   re.search(pattern, string, flags)
   re.findall(pattern, string, flags)

上述pattern是欲搜寻的正则表达方式,string是所搜寻的字符串,flags可以省略,未来会介绍几个flags常用相关参数的应用。

程序实例ch16_5.py:使用re.search( )重新设计ch16_3.py,由于省略了re.compile( ),所以读者需留意第11行内容写法。

# ch16_5.py
import re

msg1 = 'Please call my secretary using 0930-919-919 or 0952-001-001'
msg2 = '请明天17:30和我一起参加明志科大教师节晚餐'
msg3 = '请明天17:30和我一起参加明志科大教师节晚餐, 可用0933-080-080联络我'

def parseString(string):
    """解析字符串是否含有电话号码"""
    pattern = r'\d\d\d\d-\d\d\d-\d\d\d'
    phoneNum = re.search(pattern, string)
    if phoneNum != None:        # 如果phoneNum不是None表示取得号码
        print("电话号码是: %s" % phoneNum.group())
    else:
        print("%s 字符串不含电话号码" % string)

parseString(msg1)
parseString(msg2)
parseString(msg3)

执行结果 与ch16_3.py相同。

程序实例ch16_6.py:使用re.findall( )重新设计ch16_4.py,由于省略了re.compile( ),所以读者需留意第11行内容写法。

# ch16_6.py
import re

msg1 = 'Please call my secretary using 0930-919-919 or 0952-001-001'
msg2 = '请明天17:30和我一起参加明志科大教师节晚餐'
msg3 = '请明天17:30和我一起参加明志科大教师节晚餐, 可用0933-080-080联络我'

def parseString(string):
    """解析字符串是否含有电话号码"""
    pattern = r'\d\d\d\d-\d\d\d-\d\d\d'
    phoneNum = re.findall(pattern, string)   # 用列表传回搜寻结果
    print("电话号码是: %s" % phoneNum)       # 列表方式显示电话号码

parseString(msg1)
parseString(msg2)
parseString(msg3)

执行结果 与ch16_4.py相同。

16-2-6 再看正则表达式

下列是我们目前的正则表达式所搜寻的字符串模式:

 r'\d\d\d\d-\d\d\d-\d\d\d'

其中可以看到\d重复出现,对于重复出现的字符串可以用大括号内部加上重复次数方式表达,所以上述可以用下列方式表达。

 r'\d{4}-\d{3}-\d{3}'

程序实例ch16_7.py:使用本节观念重新设计ch16_6.py,下列只列出不一样的程序内容。
在这里插入图片描述

# ch16_7.py
import re

msg1 = 'Please call my secretary using 0930-919-919 or 0952-001-001'
msg2 = '请明天17:30和我一起参加明志科大教师节晚餐'
msg3 = '请明天17:30和我一起参加明志科大教师节晚餐, 可用0933-080-080联络我'

def parseString(string):
    """解析字符串是否含有电话号码"""
    pattern = r'\d{4}-\d{3}-\d{3}'
    phoneNum = re.findall(pattern, string)   # 用列表传回搜寻结果
    print("电话号码是: %s" % phoneNum)       # 列表方式显示电话号码

parseString(msg1)
parseString(msg2)
parseString(msg3)

执行结果 与ch16_4.py相同。

16-3 更多搜寻比对模式

先前我们所用的实例是手机号码,想想看如果我们改用市区电话号码的比对,台北市的电话号码如下:

 02-28350000  # 可用xx-xxxxxxxx表达

下列将以上述电话号码模式说明。

16-3-1 使用小括号分组

依照16-2节的观念,可以用下列正则表示法表达上述市区电话号码。

  r'\d\d-\d\d\d\d\d\d\d\d'

所谓括号分组是以连字符“-”区别,然后用小括号隔开群组,可以用下列方式重新规划上述表达式。也可简化为:

 r'(\d\d)-(\d\d\d\d\d\d\d\d')
 r'(\d{2})-(\d{8})'

当使用re.search( )执行比对时,未来可以使用group( )传回比对符合的不同分组,例如:group( )或group(0)传回第一个比对相符的文字与ch16_3.py观念相同。如果group(1)则传回括号的第一组文字,group(2)则传回括号的第二组文字。

程序实例ch16_8.py:使用小括号分组的观念,将分组内容输出。

# ch16_8.py
import re

msg = 'Please call my secretary using 02-26669999'
pattern = r'(\d{2})-(\d{8})'
phoneNum = re.search(pattern, msg)           # 传回搜寻结果

print("完整号码是: %s" % phoneNum.group())   # 显示完整号码
print("完整号码是: %s" % phoneNum.group(0))  # 显示完整号码
print("区域号码是: %s" % phoneNum.group(1))  # 显示区域号码
print("电话号码是: %s" % phoneNum.group(2))  # 显示电话号码

执行结果

完整号码是: 02-26669999
完整号码是: 02-26669999
区域号码是: 02
电话号码是: 26669999

如果所搜寻比对的正则表达式字符串有用小括号分组,若是使用findall( )方法处理,会传回元组(tuple)的列表(list),元组内的每个元素就是搜寻的分组内容。

程序实例ch16_9.py:使用findall( )重新设计ch16_8.py,这个实例会多增加一组电话号码。

# ch16_9.py
import re

msg = 'Please call my secretary using 02-26669999 or 02-11112222'
pattern = r'(\d{2})-(\d{8})'
phoneNum = re.findall(pattern, msg)           # 传回搜寻结果
print(phoneNum)

执行结果

[('02', '26669999'), ('02', '11112222')]

16-3-2 groups( )

注意这是groups( ),有在group后面加上s,当我们使用re.search( )搜寻字符串时,可以使用这个方法取得分组的内容。这时还可以使用2-9节的多重指定的观念,若以ch16_8.py为例,在第7行我们可以使用下列多重指定获得区域号码和当地电话号码。

  areaNum, localNum = phoneNum.groups( )  # 多重指定

程序实例ch16_10.py:重新设计ch16_8.py,分别列出区域号码与电话号码。

# ch16_10.py
import re

msg = 'Please call my secretary using 02-26669999'
pattern = r'(\d{2})-(\d{8})'
phoneNum = re.search(pattern, msg)      # 传回搜寻结果
areaNum, localNum = phoneNum.groups()   # 留意是groups()
print("区域号码是: %s" % areaNum)       # 显示区域号码
print("电话号码是: %s" % localNum)      # 显示电话号码

执行结果

区域号码是: 02
电话号码是: 26669999

16-3-3 区域号码是在小括号内

在一般电话号码的使用中,常看到区域号码是用小括号包夹,如下所示:

 (02)-26669999

在处理小括号时,方式是(和),可参考下列实例。

程序实例ch16_11.py:重新设计ch16_10.py,第4行的区域号码是(02),读者需留意第4行和第5行的设计。

# ch16_11.py
import re

msg = 'Please call my secretary using (02)-26669999'
pattern = r'(\(\d{2}\))-(\d{8})'
phoneNum = re.search(pattern, msg)      # 传回搜寻结果
areaNum, localNum = phoneNum.groups()   # 留意是groups()
print("区域号码是: %s" % areaNum)       # 显示区域号码
print("电话号码是: %s" % localNum)      # 显示电话号码

执行结果

区域号码是: (02)
电话号码是: 26669999

16-3-4 使用管道|

|(pipe)在正规表示法称管道,使用管道我们可以同时搜寻比对多个字符串,例如,想要搜寻Mary和Tom字符串,可以使用下列表示。

pattern = 'Mary|Tom'  # 注意单引号'或|旁不可留空白

程序实例ch16_12.py:管道搜寻多个字符串的实例。

# ch16_12.py
import re

msg = 'John and Tom will attend my party tonight. John is my best friend.'
pattern = 'John|Tom'                # 搜寻John和Tom
txt = re.findall(pattern, msg)      # 传回搜寻结果
print(txt)
pattern = 'Mary|Tom'                # 搜寻Mary和Tom
txt = re.findall(pattern, msg)      # 传回搜寻结果
print(txt)

执行结果

['John', 'Tom', 'John']
['Tom']

16-3-5 多个分组的管道搜寻

假设有一个字符串内容如下:

 Johnson, Johnnason and Johnnathan will attend my party tonight.

由上述可知,如果想要搜寻字符串比对John后面可以是son、nason、nathan任一个字符串的组合,可以使用下列正则表达式格式:

 pattern = John(son|nason|nathan)

程序实例ch16_13.py:搜寻Johnson、Johnnason或Johnnathan任一字符串,然后列出结果,这个程序将列出第一个搜寻比对到的字符串。

# ch16_13.py
import re

msg = 'Johnson, Johnnason and Johnnathan will attend my party tonight.'
pattern = 'John(son|nason|nathan)'
txt = re.search(pattern,msg)    # 传回搜寻结果
print(txt.group())              # 打印第一个搜寻结果
print(txt.group(1))             # 打印第一个分组

执行结果

Johnson
son

同样的正则表达式若是使用findall( )方法处理,将只传回各分组搜寻到的字符串,如果要列出完整的内容,可以用循环同时为每个分组字符串加上前导字符串John。

程序实例ch16_14.py:使用findall( )重新设计ch16_13.py。

# ch16_14.py
import re

msg = 'Johnson, Johnnason and Johnnathan will attend my party tonight.'
pattern = 'John(son|nason|nathan)'
txts = re.findall(pattern,msg)      # 传回搜寻结果
print(txts)
for txt in txts:                    # 将搜寻到内容加上John
    print('John'+txt)

执行结果

['son', 'nason', 'nathan']
Johnson
Johnnason
Johnnathan

16-3-6 使用?号做搜寻

在正则表达式中若某些括号内的字符串或正则表达式可有可无,执行搜寻时皆算成功,例如,na字符串可有可无,表达方式是(na)?。

程序实例ch16_15.py:使用?搜寻的实例,这个程序会测试2次。

# ch16_15.py
import re
# 测试1
msg = 'Johnson will attend my party tonight.'
pattern = 'John((na)?son)'
txt = re.search(pattern,msg)      # 传回搜寻结果
print(txt.group())
# 测试2
msg = 'Johnnason will attend my party tonight.'
pattern = 'John((na)?son)'
txt = re.search(pattern,msg)      # 传回搜寻结果
print(txt.group())

执行结果

Johnson
Johnnason

有时候如果居住在同一个城市,在留电话号码时,可能不会留区域号码,这时就可以使用本功能了。请参考下列实例第11行。

程序实例ch16_16.py:这个程序在搜寻电话号码时,即使省略区域号码程序也可以搜寻到此号码,然后打印出来,正则表达式格式请留意第6行。

# ch16_16.py
import re

# 测试1
msg = 'Please call my secretary using 02-26669999'
pattern = r'(\d\d-)?(\d{8})'                 # 增加?号
phoneNum = re.search(pattern, msg)           # 传回搜寻结果
print("完整号码是: %s" % phoneNum.group())   # 显示完整号码

# 测试2
msg = 'Please call my secretary using 26669999'
pattern = r'(\d\d-)?(\d{8})'                 # 增加?号
phoneNum = re.search(pattern, msg)           # 传回搜寻结果
print("完整号码是: %s" % phoneNum.group())   # 显示完整号码

执行结果

完整号码是: 02-26669999
完整号码是: 26669999

16-3-7 使用*号做搜寻

在正则表达式中若某些字符串或正则表达式可从0到多次,执行搜寻时皆算成功,例如,na字符串可从0到多次,表达方式是(na)*。

程序实例ch16_17.py:这个程序的重点是第5行的正则表达式,其中字符串na的出现次数可以是从0到多次。

# ch16_17.py
import re
# 测试1
msg = 'Johnson will attend my party tonight.'
pattern = 'John((na)*son)'        # 字符串na可以0到多次
txt = re.search(pattern,msg)      # 传回搜寻结果
print(txt.group())
# 测试2
msg = 'Johnnason will attend my party tonight.'
pattern = 'John((na)*son)'        # 字符串na可以0到多次
txt = re.search(pattern,msg)      # 传回搜寻结果
print(txt.group())
# 测试3
msg = 'Johnnananason will attend my party tonight.'
pattern = 'John((na)*son)'        # 字符串na可以0到多次
txt = re.search(pattern,msg)      # 传回搜寻结果
print(txt.group())

执行结果

Johnson
Johnnason
Johnnananason

16-3-8 使用+号做搜寻

在正则表达式中若是某些字符串或正则表达式可从1到多次,执行搜寻时皆算成功,例如,na字符串可从1到多次,表达方式是(na)+。

程序实例ch16_18.py:这个程序的重点是第5行的正则表达式,其中字符串na的出现次数可以是从1到多次。

# ch16_18.py
import re
# 测试1
msg = 'Johnson will attend my party tonight.'
pattern = 'John((na)+son)'        # 字符串na可以1到多次
txt = re.search(pattern,msg)      # 传回搜寻结果
print(txt)                        # 请注意是直接打印对象
# 测试2
msg = 'Johnnason will attend my party tonight.'
pattern = 'John((na)+son)'        # 字符串na可以1到多次
txt = re.search(pattern,msg)      # 传回搜寻结果
print(txt.group())
# 测试3
msg = 'Johnnananason will attend my party tonight.'
pattern = 'John((na)+son)'        # 字符串na可以1到多次
txt = re.search(pattern,msg)      # 传回搜寻结果
print(txt.group())

执行结果

None
Johnnason
Johnnananason

16-3-9 搜寻时忽略大小写

搜寻时若是在search( )或findall( )内增加第三个参数re.I或re.IGNORECASE,搜寻时就会忽略大小写,至于打印输出时将以原字符串的格式显示。

程序实例ch16_19.py:以忽略大小写方式执行找寻相符字符串。

# ch16_19.py
import re

msg = 'john and TOM will attend my party tonight. JOHN is my best friend.'
pattern = 'John|Tom'                        # 搜寻John和Tom
txt = re.findall(pattern, msg, re.I)        # 传回搜寻忽略大小写的结果
print(txt)
pattern = 'Mary|tom'                        # 搜寻Mary和tom
txt = re.findall(pattern, msg, re.I)        # 传回搜寻忽略大小写的结果
print(txt)

执行结果

['john', 'TOM', 'JOHN']
['TOM']

16-4 贪婪与非贪婪搜寻

16-4-1 搜寻时使用大括号设定比对次数

在16-2-6节我们有使用过大括号,当时讲解\d{4}代表重复4次,也就是大括号的数字是设定重复次数。可以将这个观念应用在搜寻一般字符串,例如,(son){3}代表所搜寻的字符串是‘sonsonson’,如果有一字符串是‘sonson’,则搜寻结果是不符。大括号除了可以设定重复次数,也可以设定指定范围,例如,(son){3,5}代表所搜寻的字符串如果是‘sonsonson’‘sonsonsonson’或‘sonsonsonsonson’皆算是相符合的字符串。(son){3,5}正则表达式相当于下列表达式:

((son)(son)(son))|((son)(son)(son)(son))|((son)(son)(son)(son)(son))

程序实例ch16_20.py:设定搜寻son字符串重复3-5次皆算搜寻成功。

# ch16_20.py
import re

def searchStr(pattern, msg):
    txt = re.search(pattern, msg)
    if txt == None:         # 搜寻失败
        print("搜寻失败 ",txt)
    else:                   # 搜寻成功
        print("搜寻成功 ",txt.group())

msg1 = 'son'
msg2 = 'sonson'
msg3 = 'sonsonson'
msg4 = 'sonsonsonson'
msg5 = 'sonsonsonsonson'
pattern = '(son){3,5}'
searchStr(pattern,msg1)
searchStr(pattern,msg2)
searchStr(pattern,msg3)
searchStr(pattern,msg4)
searchStr(pattern,msg5)

执行结果

搜寻失败  None
搜寻失败  None
搜寻成功  sonsonson
搜寻成功  sonsonsonson
搜寻成功  sonsonsonsonson

使用大括号时,也可以省略第一或第二个数字,这相当于不设定最小或最大重复次数。例如:(son){3,}代表重复3次以上皆符合,(son){,10}代表重复10次以下皆符合。有关这方面的实作,将留给读者练习,可参考习题3。

16-4-2 贪婪与非贪婪搜寻

在讲解贪婪与非贪婪搜寻前,笔者先简化程序实例ch16_20.py,使用相同的搜寻模式‘(son){3,5}’,搜寻字符串是‘sonsonsonsonson’,看看结果。

程序实例ch16_21.py:使用搜寻模式‘(son){3,5}’,搜寻字符串‘sonsonsonsonson’。

# ch16_21.py
import re

def searchStr(pattern, msg):
    txt = re.search(pattern, msg)
    if txt == None:         # 搜尋失敗
        print("搜尋失敗 ",txt)
    else:                   # 搜尋成功
        print("搜尋成功 ",txt.group())

msg = 'sonsonsonsonson'
pattern = '(son){3,5}'
searchStr(pattern,msg)

执行结果

搜尋成功  sonsonsonsonson

其实由上述程序所设定的搜寻模式可知3、4或5个son重复就算找到了,可是Python执行结果是列出最多重复的字符串,5次重复,这是Python的默认模式,这种模式又称贪婪(greedy)模式。

另一种是列出最少重复的字符串,以这个实例而言是重复3次,这称非贪婪模式,方法是在正则表达式的搜寻模式右边增加?符号。

程序实例ch16_22.py:以非贪婪模式重新设计ch16_21.py,请读者留意第12行的正则表达式的搜寻模式最右边的?符号。
在这里插入图片描述

# ch16_22.py
import re

def searchStr(pattern, msg):
    txt = re.search(pattern, msg)
    if txt == None:         # 搜寻失败
        print("搜寻失败 ",txt)
    else:                   # 搜寻成功
        print("搜寻成功 ",txt.group())

msg = 'sonsonsonsonson'
pattern = '(son){3,5}?'     # 非贪婪模式
searchStr(pattern,msg)

执行结果

搜寻成功  sonsonson

16-5 正则表达式的特殊字符

为了不让一开始学习正则表达式太复杂,在前面4个小节笔者只介绍了\d,同时穿插介绍一些字符串的搜寻。我们知道\d代表的是数字字符,也就是从0-9的阿拉伯数字,如果使用管道|的观念,\d相当于是下列正则表达式:

(0|1|2|3|4|5|6|7|8|9)

这一节将针对正则表达式的特殊字符做一个完整的说明。

16-5-1 特殊字符表

在这里插入图片描述

下列是一些使用上述表格观念的正则表达式的实例说明。

程序实例ch16_23.py:将一段英文句子的单词分离,同时将英文单词前4个字母是“John”的单词筛选出来。笔者设定如下:

 pattern = ‘\w+'      # 意义是把不限长度的数字、字母和底线字符当作符合搜寻
 pattern = ‘John\w*'  # John开头后面接0~多个数字、字母和底线字符
# ch16_23.py
import re
# 测试1将字符串从句子分离
msg = 'John, Johnson, Johnnason and Johnnathan will attend my party tonight.'
pattern = '\w+'                    # 不限长度的单字
txt = re.findall(pattern,msg)      # 传回搜寻结果
print(txt)
# 测试2将John开始的字符串分离
msg = 'John, Johnson, Johnnason and Johnnathan will attend my party tonight.'
pattern = 'John\w*'                # John开头的单字
txt = re.findall(pattern,msg)      # 传回搜寻结果
print(txt)

执行结果

['John', 'Johnson', 'Johnnason', 'and', 'Johnnathan', 'will', 'attend', 'my', 'party', 'tonight']
['John', 'Johnson', 'Johnnason', 'Johnnathan']

程序实例ch16_24.py:正则表达式的应用,下列程序重点是第5行。

\d+:表示不限长度的数字。

\s:表示空格。

\w+:表示不限长度的数字、字母和底线字符连续字符。

# ch16_24.py
import re

msg = '1 cat, 2 dogs, 3 pigs, 4 swans'
pattern = '\d+\s\w+'
txt = re.findall(pattern,msg)      # 传回搜寻结果
print(txt)

执行结果

['1 cat', '2 dogs', '3 pigs', '4 swans']

16-5-2 字符分类

Python可以使用中括号来设定字符,可参考下列范例。

[a-z]:代表a-z的小写字符。

[A-Z]:代表A-Z的大写字符。

[aeiouAEIOU]:代表英文发音的元音字符。

[2-5]:代表2-5的数字。

在字符分类中,中括号内可以不用放上正则表示法的反斜杠\执行.、?、*、(、)等字符的转译。例如,[2-5.]会搜寻2-5的数字和句点,这个语法不用写成[2-5.]。

程序实例ch16_25.py:搜寻字符的应用,这个程序首先将搜寻[aeiouAEIOU],然后将搜寻[2-5.]。

# ch16_25.py
import re
# 测试1搜寻[aeiouAEIOU]字符
msg = 'John, Johnson, Johnnason and Johnnathan will attend my party tonight.'
pattern = '[aeiouAEIOU]'           
txt = re.findall(pattern,msg)      # 传回搜寻结果
print(txt)
# 测试2搜寻[2-5.]字符
msg = '1. cat, 2. dogs, 3. pigs, 4. swans'
pattern = '[2-5.]'
txt = re.findall(pattern,msg)      # 传回搜寻结果
print(txt)

执行结果

['o', 'o', 'o', 'o', 'a', 'o', 'a', 'o', 'a', 'a', 'i', 'a', 'e', 'a', 'o', 'i']
['.', '2', '.', '3', '.', '4', '.']

16-5-3 字符分类的^字符

在16-5-2小节字符的处理中,如果在中括号内的左方加上^字符,意义是搜寻不在这些字符内的所有字符。

程序实例ch16_26.py:使用字符分类的^字符重新设计ch16_25.py。

# ch16_26.py
import re
# 测试1搜寻不在[aeiouAEIOU]的字符
msg = 'John, Johnson, Johnnason and Johnnathan will attend my party tonight.'
pattern = '[^aeiouAEIOU]'           
txt = re.findall(pattern,msg)      # 传回搜寻结果
print(txt)
# 测试2搜寻不在[2-5.]的字符
msg = '1. cat, 2. dogs, 3. pigs, 4. swans'
pattern = '[^2-5.]'
txt = re.findall(pattern,msg)      # 传回搜寻结果
print(txt)

执行结果

['J', 'h', 'n', ',', ' ', 'J', 'h', 'n', 's', 'n', ',', ' ', 'J', 'h', 'n', 'n', 's', 'n', ' ', 'n', 'd', ' ', 'J', 'h', 'n', 'n', 't', 'h', 'n', ' ', 'w', 'l', 'l', ' ', 't', 't', 'n', 'd', ' ', 'm', 'y', ' ', 'p', 'r', 't', 'y', ' ', 't', 'n', 'g', 'h', 't', '.']
['1', ' ', 'c', 'a', 't', ',', ' ', ' ', 'd', 'o', 'g', 's', ',', ' ', ' ', 'p', 'i', 'g', 's', ',', ' ', ' ', 's', 'w', 'a', 'n', 's']

上述第一个测试结果不会出现[aeiouAEIOU]字符,第二个测试结果不会出现[2-5.]字符。

16-5-4 正则表示法的^字符

这个字符与16-5-3小节的字符完全相同,但是用在不一样的地方,意义不同。在正规表示法中起始位置加上^字符,表示正则表示法的字符串必须出现在被搜寻字符串的起始位置,这样搜寻成功才算成功。

程序实例ch16_27.py:正则表示法^字符的应用,测试1字符串John是在最前面所以可以得到搜寻结果,测试2字符串John不是在最前面,结果搜寻失败传回空字符串。

# ch16_27.py
import re
# 测试1搜寻John字符串在最前面
msg = 'John will attend my party tonight.'
pattern = '^John'           
txt = re.findall(pattern,msg)      # 传回搜寻结果
print(txt)
# 测试2搜寻John字符串不是在最前面
msg = 'My best friend is John'
pattern = '^John'
txt = re.findall(pattern,msg)      # 传回搜寻结果
print(txt)

执行结果

['John']
[]

16-5-5 正则表示法的$字符

正则表示法的末端放置$字符时,表示正则表示法的字符串必须出现在被搜寻字符串的最后位置,这样搜寻成功才算成功。

程序实例ch16_28.py:正则表示法$字符的应用,测试1是搜寻字符串结尾是非英文字符、数字和底线字符,由于结尾字符是“.”,所以传回所搜寻到的字符。测试2是搜寻字符串结尾是非英文字符、数字和底线字符,由于结尾字符是“8”,所以传回搜寻结果是空字符串。测试3是搜寻字符串结尾是数字字符,由于结尾字符是“8”,所以传回搜寻结果“8”。测试4是搜寻字符串结尾是数字字符,由于结尾字符是“.”,所以传回搜寻结果空字符串。

# ch16_28.py
import re
# 测试1搜寻最后字符是非英文字母数字和底线字符
msg = 'John will attend my party 28 tonight.'
pattern = '\W$'           
txt = re.findall(pattern,msg)      # 传回搜寻结果
print(txt)
# 测试2搜寻最后字符是非英文字母数字和底线字符
msg = 'I am 28'
pattern = '\W$'
txt = re.findall(pattern,msg)      # 传回搜寻结果
print(txt)
# 测试3搜寻最后字符是数字
msg = 'I am 28'
pattern = '\d$'
txt = re.findall(pattern,msg)      # 传回搜寻结果
print(txt)
# 测试4搜寻最后字符是数字
msg = 'I am 28 year old.'
pattern = '\d$'
txt = re.findall(pattern,msg)      # 传回搜寻结果
print(txt)

执行结果

['.']
[]
['8']
[]

我们也可以将16-5-4小节的^字符和$字符混合使用,这时如果既要符合开始字符串也要符合结束字符串,所以被搜寻的句子一定要只有一个字符串。

程序实例ch16_29.py:搜寻开始到结束皆是数字的字符串,字符串内容只要有非数字字符就算搜寻失败。测试2中由于中间有非数字字符,所以搜寻失败。读者应留意程序第5行的正则表达式的写法。

# ch16_29.py
import re
# 测试1搜寻开始或结尾皆是数字的字符串
msg = '09282028222'
pattern = '^\d+$'           
txt = re.findall(pattern,msg)      # 传回搜寻结果
print(txt)
# 测试2搜寻开始或结尾皆是数字的字符串
msg = '0928tuyr990'
pattern = '^\d+$'
txt = re.findall(pattern,msg)      # 传回搜寻结果
print(txt)

执行结果

['09282028222']
[]

16-5-6 单一字符使用通配符“.”

通配符(wildcard)“.”表示可以搜寻除了换行字符以外的所有字符,但是只限定一个字符。

程序实例ch16_30.py:通配符的应用,搜寻一个通配符加上at,在下列输出中,第4个由于at符合,Python自动加上空格符。第6个由于只能加上一个字符,所以搜寻结果是lat。

# ch16_30.py
import re
msg = 'cat hat sat at matter flat'
pattern = '.at'           
txt = re.findall(pattern,msg)      # 传回搜寻结果
print(txt)

执行结果

['cat', 'hat', 'sat', ' at', 'mat', 'lat']

如果搜寻的是真正的“.”字符,须使用反斜杠“.”。

16-5-7 所有字符使用通配符“.*”

若是将16-3-7小节所介绍的“.”字符与“*”组合,可以搜寻所有字符,意义是搜寻0到多个通配符(换行字符除外)。

程序实例ch16_31.py:搜寻所有字符“.*”的组合应用。

# ch16_31.py
import re

msg = 'Name: Jiin-Kwei Hung Address: 8F, Nan-Jing E. Rd, Taipei'
pattern = 'Name: (.*) Address: (.*)'
txt = re.search(pattern,msg)      # 传回搜寻结果
Name, Address = txt.groups()
print("Name:    ", Name)
print("Address: ", Address)

执行结果

Name:     Jiin-Kwei Hung
Address:  8F, Nan-Jing E. Rd, Taipei

16-5-8 换行字符的处理

使用16-5-7小节观念用“.*”搜寻时碰上换行字符,搜寻就停止。Python的re模块提供参数re.DOTALL,功能是包括搜寻换行字符,可以将此参数放在search( )、findall( )或compile( )。

程序实例ch16_32.py:测试1是搜寻除换行字符以外的字符,测试2是搜寻含换行字符的所有字符。由于测试2有包含换行字符,所以输出时,换行字符主导分2行输出。

# ch16_32.py
import re
#测试1搜寻除了换行字符以外字符
msg = 'Name: Jiin-Kwei Hung \nAddress: 8F, Nan-Jing E. Rd, Taipei'
pattern = '.*'
txt = re.search(pattern,msg)           # 传回搜寻不含换行字符结果
print("测试1输出: ", txt.group())
#测试2搜寻包括换行字符
msg = 'Name: Jiin-Kwei Hung \nAddress: 8F, Nan-Jing E. Rd, Taipei'
pattern = '.*'
txt = re.search(pattern,msg,re.DOTALL) # 传回搜寻含换行字符结果
print("测试2输出: ", txt.group())

执行结果

测试1输出:  Name: Jiin-Kwei Hung 
测试2输出:  Name: Jiin-Kwei Hung
Address: 8F, Nan-Jing E. Rd, Taipei

16-6 MatchObject对象

16-2节已经讲解使用re.search( )搜寻字符串,搜寻成功时可以产生MatchObject对象,这里将先介绍另一个搜寻对象的方法re.match( ),这个方法搜寻成功后也将产生MatchObject对象。接着本节会分成几个小节,再讲解MatchObject几个重要的方法(method)。

16-6-1 re.match( )

这本书已经讲解了搜寻字符串中最重要的2个方法re.search( )和re.findall( ),re模块另一个方法是re.match( ),这个方法其实和re.search( )相同,差异是re.match( )只搜寻比对字符串开始的字,如果失败就算失败。re.search( )则是搜寻整个字符串。至于re.match( )搜寻成功会传回MatchObject对象,若是搜寻失败会传回None,这部分与re.search( )相同。

程序实例ch16_33.py:re.match( )的应用。测试1是将John放在被搜寻字符串的最前面,测试2没有将John放在被搜寻字符串的最前面。

# ch16_33.py
import re
#测试1搜寻使用re.match()
msg = 'John will attend my party tonight.'  # John是第一个字符串
pattern = 'John'
txt = re.match(pattern,msg)                 # 传回搜寻结果
if txt != None:
    print("测试1输出: ", txt.group())
else:
    print("测试1搜寻失败")
#测试2搜寻使用re.match()
msg = 'My best friend is John.'             # John不是第一个字符串
txt = re.match(pattern,msg,re.DOTALL)       # 传回搜寻结果
if txt != None:
    print("测试2输出: ", txt.group())
else:
    print("测试2搜寻失败")

执行结果

测试1输出:  John
测试2搜寻失败

16-6-2 MatchObject几个重要的方法

当使用re.search( )或re.match( )搜寻成功时,会产生MatchOjbect对象。

程序实例ch16_34.py:看看MatchObject对象是什么。

# ch16_34.py
import re
#测试1搜寻使用re.match()
msg = 'John will attend my party tonight.'  
pattern = 'John'
txt = re.match(pattern,msg)                 # re.match()
if txt != None:
    print("使用re.match()输出MatchObject对象:  ", txt)
else:
    print("测试1搜寻失败")
#测试1搜寻使用re.search()
txt = re.search(pattern,msg)                # re.search()
if txt != None:
    print("使用re.search()输出MatchObject对象: ", txt)
else:
    print("测试1搜寻失败")

执行结果

使用re.match()输出MatchObject对象:   <re.Match object; span=(0, 4), match='John'>
使用re.search()输出MatchObject对象:  <re.Match object; span=(0, 4), match='John'>   

从上述可知,当使用re.match( )和re.search( )皆搜寻成功时,两者的MatchObject对象内容是相同的。span是注明成功搜寻字符串的起始位置和结束位置,从此处可以知道起始索引位置是0,结束索引位置是4。match则是注明成功搜寻的字符串内容。

Python提供下列取得MatchObject对象内容的重要方法。

在这里插入图片描述

程序实例ch16_35.py:分别使用re.match( )和re.search( )搜寻字符串Joah,成功搜寻到字符串时,分别用start( )、end( )和span( )方法列出字符串出现的位置。

# ch16_35.py
import re
#测试1搜寻使用re.match()
msg = 'John will attend my party tonight.'  
pattern = 'John'
txt = re.match(pattern,msg)                 # re.match()
if txt != None:
    print("搜寻成功字符串的起始索引位置 :  ", txt.start())
    print("搜寻成功字符串的结束索引位置 :  ", txt.end())
    print("搜寻成功字符串的结束索引位置 :  ", txt.span())
#测试2搜寻使用re.search()
msg = 'My best friend is John.'  
txt = re.search(pattern,msg)                # re.search()
if txt != None:
    print("搜寻成功字符串的起始索引位置 :  ", txt.start())
    print("搜寻成功字符串的结束索引位置 :  ", txt.end())
    print("搜寻成功字符串的结束索引位置 :  ", txt.span())

执行结果

搜寻成功字符串的起始索引位置 :   0
搜寻成功字符串的结束索引位置 :   4
搜寻成功字符串的结束索引位置 :   (0, 4)
搜寻成功字符串的起始索引位置 :   18
搜寻成功字符串的结束索引位置 :   22
搜寻成功字符串的结束索引位置 :   (18, 22)

16-7 抢救CIA情报员-sub( )方法

Python re模块内的sub( )方法可以用新的字符串取代原本字符串的内容。

16-7-1 一般的应用

sub( )方法的基本使用语法如下:

 result = re.sub(pattern, newstr, msg)  # msg是整个欲处理的字符串或句子

pattern是欲搜寻的字符串,如果搜寻成功则用newstr取代,同时成功取代的结果回传给result变量,如果搜寻到多个相同字符串,这些字符串将全部被取代,需留意原先msg内容将不会改变。如果搜寻失败则将msg内容回传给result变量,当然msg内容也不会改变。

程序实例ch16_36.py:这是字符串取代的应用,测试1是发现2个字符串被成功取代(Eli Nan被Kevin Thomson取代),同时列出取代结果。测试2是取代失败,所以txt与原msg内容相同。

# ch16_36.py
import re
#测试1取代使用re.sub()结果成功
msg = 'Eli Nan will attend my party tonight. My best friend is Eli Nan'  
pattern = 'Eli Nan'                 # 欲搜寻字符串        
newstr = 'Kevin Thomson'            # 新字符串
txt = re.sub(pattern,newstr,msg)    # 如果找到则取代
if txt != msg:                      # 如果txt与msg内容不同表示取代成功
    print("取代成功: ", txt)        # 列出成功取代结果
else:
    print("取代失败: ", txt)        # 列出失败取代结果
#测试2取代使用re.sub()结果失败  
pattern = 'Eli Thomson'             # 欲搜寻字符串        
txt = re.sub(pattern,newstr,msg)    # 如果找到则取代           
if txt != msg:                      # 如果txt与msg内容不同表示取代成功
    print("取代成功: ", txt)        # 列出成功取代结果
else:
    print("取代失败: ", txt)        # 列出失败取代结果

执行结果

取代成功:  Kevin Thomson will attend my party tonight. My best friend is Kevin Thomson
取代失败:  Eli Nan will attend my party tonight. My best friend is Eli Nan

16-7-2 抢救CIA情报员
社会上有太多需要保护当事人隐私权利的场合,例如,情报机构在内部文件不可直接将情报员的名字列出来,历史上太多这类实例造成情报员的牺牲,这时可以使用***代替原本的姓名。使用Python的正则表示法,可以轻松协助我们执行这方面的工作。这一节将先用程序代码,然后解析此程序。

程序实例ch16_37.py:将CIA情报员名字,用名字第一个字母和***取代。

# ch16_37.py
import re
# 使用隐藏文字执行取代
msg = 'CIA Mark told CIA Linda that secret USB had given to CIA Peter.'
pattern = r'CIA (\w)\w*'            # 欲搜寻FBI + 空一格后的名字        
newstr = r'\1***'                   # 新字符串使用隐藏文字
txt = re.sub(pattern,newstr,msg)    # 执行取代
print("取代成功: ", txt)            # 列出取代结果

执行结果

取代成功:  M*** told L*** that secret USB had given to P***.

上述程序第一个关键是第5行,这一行将搜寻CIA字符串外加空一格后出现不限长度的字符串(可以由英文大小写或数字或底线所组成)。观念是括号内的(\w)代表必须只有一个字符,同时小括号代表这是一个分组(group),由于整行只有一个括号所以知道这是第一分组,同时只有一个分组,括号外的\w表示可以有0到多个字符。所以(\w)\w相当于是1-多个字符组成的单字,同时存在分组1。

上述程序第6行的\1代表用分组1找到的第一个字母当作字符串开头,后面则是接在第一个字母后的字符。对CIA Mark而言所找到的第一个字母是M,所以取代的结果是M。对CIA Linda而言所找到的第一个字母是L,所以取代的结果是L***。对CIA Peter而言所找到的第一个字母是P,所以取代的结果是P***。

16-8 处理比较复杂的正则表示法

有一个正则表示法内容如下:
在这里插入图片描述

其实相信大部分的读者看到上述正则表示法,就想弃械投降了,坦白说的确复杂,不过不用担心,笔者将一步步解析,让事情变简单。

16-8-1 将正则表达式拆成多行字符串

在3-4-2小节笔者有介绍可以使用3个单引号(或是双引号)将过长的字符串拆成多行表达,这个观念也可以应用在正则表达式,当我们适当地拆解后,可以为每一行加上批注,整个正则表达式就变得简单了。若是将上述pattern,拆解成下列表示法,整个就变得简单了。
在这里插入图片描述

接下来笔者分别解释相信读者就可以了解了,第一行区域号码是2位数,可以接受有括号的区域号码,也可以接受没有括号的区域号码,例如,02或(02)皆可以。第二行是设定区域号码与电话号码间的字符,可以接受空格符或-字符当作分隔符。第三行是设定8位数数字的电话号码。第四行是分机号码,分机号码可以用ext或ext.当作起始字符,空一定格数,然后接受2-4位数的分机号码。

16-8-2 re.VERBOSE

使用Python时,如果想在正则表达式中加上批注,可参考16-8-1小节,必须配合使用re.VERBOSE参数,然后将此参数放在search( )、findall( )或compile( )。

程序实例ch16_38.py:搜寻市区电话号码的应用,这个程序可以搜寻下列格式的电话号码。
在这里插入图片描述

# ch16_38.py
import re

msg = '''02-88223349, (02)-26669999, 02-29998888 ext 123,
        12345678, 02 33887766 ext. 12222'''
pattern = r'''(
    (\d{2}|\(\d{2}\))?              # 区域号码
    (\s|-)?                         # 区域号码与电话号码的分隔符
    \d{8}                           # 电话号码
    (\s*(ext|ext.)\s*\d{2,4})?      # 2-4位数的分机号码
    )'''
phoneNum = re.findall(pattern, msg, re.VERBOSE)     # 传回搜寻结果
print(phoneNum)

执行结果

[('02-88223349', '02', '-', '', ''), ('(02)-26669999', '(02)', '-', '', ''), ('02-29998888 ext 123', '02', '-', ' ext 123', 'ext'), (' 12345678', '', ' ', '', ''), ('02 33887766 ext. 1222', '02', ' ', ' ext. 1222', 'ext.')]

16-8-3 电子邮件地址的搜寻

在字处理过程中,必须在文件内将电子邮件地址解析出来很常见,下列是这方面的应用。下列是Pattern内容。

在这里插入图片描述

第1行用户账号常用的有a-z字符、A-Z字符、0-9数字、底线_、点.。第2行是@符号。第3行是主机域名,常用的有a-z字符、A-Z字符、0-9数字、分隔符-、点.。第4行是点 . 符号。第5行最常见的是com或edu,也可能是cc或其他,这通常由2至4个字符组成,常用的有a-z字符、A-Z字符。第6行是点 . 符号,在美国通常只要前5行就够了,但是在其他国家则常常需要此字段,所以此字段后面是?字符。第7行通常是国别,例如中国是cn、日本是ja,常用的有a-z字符、A-Z字符。

程序实例ch16_39.py:电子邮件地址的搜寻。

# ch16_39.py
import re

msg = '''txt@deepstone.com.tw kkk@gmail.com'''
pattern = r'''(
    [a-zA-Z0-9_.]+                  # 使用者账号
    @                               # @符号
    [a-zA-Z0-9-.]+                  # 主机域名domain
    [\.]                            # .符号
    [a-zA-Z]{2,4}                   # 可能是com或edu或其它
    ([\.])?                         # .符号, 也可能无特别是美国
    ([a-zA-Z]{2,4})?                # 国别
    )'''
eMail = re.findall(pattern, msg, re.VERBOSE)     # 传回搜寻结果
print(eMail)

执行结果

[('txt@deepstone.com.tw', '', ''), ('kkk@gmail.com', '', '')]

16-8-4 re.IGNORECASE/re.DOTALL/re.VERBOSE

在16-3-9小节笔者介绍了re.IGNORECASE参数,在16-5-8小节笔者介绍了re.DOTALL参数,在16-8-2小节笔者介绍了re.VERBOSE参数,我们可以分别在re.search( )、re.findall( )、re.match( )或是re.compile( )方法内使用它们,可是一次只能放置一个参数,如果我们想要一次放置多个参数特性,应如何处理?方法是使用16-3-4小节的管道|观念,例如,可以使用下列方式:

 datastr = re.search(pattern, msg, re.IGNORECASE|re.DOTALL|re.VERBOSE)

其实这一章已经讲解了相当多的正则表达式的知识了,未来各位在写论文、做研究或职场上相信会有相当帮助。如果仍觉不足,可以自行到Python官网获得更多正则表达式的知识。

在这里插入图片描述

  • 14
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值