15.1 正则表达式为高级文本模式匹配,以及搜索-替代等功能提供了基础。正则表达式(RE)是一些由字符和特殊符号组成的字符串,它们描述了这些字符和字符的某种重复方式,因此能按某种模式匹配一个有相似特征的字符串的集合,因此能按某模式匹配一系列有相似特征的字符串。
搜索与匹配:搜索,即在字符串任意部分中搜索匹配的模式,而匹配是指,判断一个字符串能否从起始处全部或部分的匹配某个模式
15.2 正则表达式使用的特殊符号和字符
15.2.1 用管道符号(|)匹配多个正则表达式模式
(|)表示一个或的操作
正则表达式模式 匹配的字符串
at|home at,home
r2d2|c3po red2, c3po
15.2.2 匹配任意一个单个的字符(.)
点字符或句点(.)符号匹配除换行符外的任意一个单个字符。
15.2.3 从字符串的开头或结尾或单词边界开始匹配(^/$/\b/\B)
15.2.4 创建字符类([])
正则表达式模式 配的字符串
[cr] [23] [dp] [o2] 可以为c2d2,r3po,.......
15.2.5 指定范围(-)和否定(^)
正则表达式模式 匹配的字符
z.[0-9] 字符z,后面跟任意一个字符
[^aeiou] 一个非元音字符
15.2.6 使用闭包操作符(*,+,?,{})实现多次出现/重复匹配
星号或称星号操作符匹配它左边那个正则表达式出现0次或0次以上的情况
加号操作符匹配它左边那个正则表达式至少出现一次的情况
问号操作符匹配它左边那个正则表达式出现零次或一次的情况
花括号操作符里可以是单一的值,也可以是由逗号分开的一对值,如果是一个值,则表示匹配N次出现,如果
是一对值,如{M,N}就表示匹配M次到N次出现
正则表达式模式 匹配的字符
[dn]ot? d 或n 后面可接t,可不接
0?[1-9] 可以有0,可以无0
[0-9]{15,16} 15位或16位数字表示,如信用卡号码
</?[^>]>+> 匹配所有的合法的HTML标签的字符串
15.2.7 特殊字符表示,字符集
正则表达式模式 匹配的字符串
\w+-\d+ 由一个字母或数字组成的字符串和至少一个数字,两部分中间由连字符连接
[A-Za-z]\w* 第一个字符是字母,其余字符(如果有)是字母或者数字
\d{3}-\d(3)-\d(4) 电话号码
\w+@\w+\.com 简单的邮件地址
15.2.8 用圆括号(())组建组
对正则表达式进行分组,如,你需要两个不同的正则表达式去比较一个字符串,另一个理由是为整个正则表达式添加一个重复操作符
正则表达式模式 匹配的字符串
\d+(\.\d*)? 表示简单的浮点数,即,任意个十进制数字,后面跟一个可选的小数点,然后再接0或者多个十进制数字
(Mr?s?\. )?[A-Z][a-z]* [ A-Za-z-]+ 名字和姓氏,对名字的限制(首字母大写,其它字 母(如果存在)小写), 姓氏没有什么限制,允许有多个单词、横线、大写字母
15.3 正则表达式和Python语言
15.3.1 re模块:核心函数和方法
compile()函数、match()函数、search()函数
15.3.2 使用compile()编译正则表达式
编译
15.3.3匹配对象和group()、groups()方法
group()方法或者返回所有匹配对象或是根据要求返回某个特定子组
groups()返回一个包含全部匹配的子组的元组
15.3.4用match()匹配字符串
match()函数尝试从字符串的开头开始对模式进行匹配,如果匹配成功,就返回一个匹配对象,如果匹配失败,就返回None
import re
m=re.match('foo','foo')
if m is not None:
print m.group()
如果字符串比较长,匹配也可能成功
如:re.match('foo','food on the table').group()
15.3.5 search()在一个字符串中查找一个模式
search()查找字符串中模式首次出现的位置,而不是尝试在起始处匹配。严格的说,search是从左到右进行搜索的。
import re
m=re.search('foo','testfoo')
if m is not None:
print m.group()
15.3.6 匹配任意多个字符串 (|)
相当于或,用match匹配,search查找
15.3.7 匹配任意单个字符(.)
不能匹配非字符(如空字符串)和换行符,空格是可以匹配的
如 anyend = .end
m=re.match('anyend','\nend')
15.3.8 创建字符集和([ ])
15.3.9重复、特殊字符和子组
import re
patt = '\w+@(\w+\.)?\w+\.com'
print re.match(patt,'dxf@ly.com').group()
m = re.match('\w\w\w-\d\d\d','abc-123')
m1 = re.match('(\w\w\w)-(\d\d\d)','abc-123')
print m1.group()
print m1.group(1)
print m1.group(2)
print m1.groups()
15.3.10 从字符串的开头或结尾匹配及在单词边界上的匹配
主要用于search()
m = re.search('^The','The end')
m= re.search('\bthe','bite the dog')
15.3.11 用findall()找到每个出现的匹配部分
findall()和search()相似之处在于两者都执行字符串搜索,但findall()和match()与search()不同之处是,findall总返回一个列表
import re
m = re.findall('car','carry the barcardi to the car')
print m
15.3.12 用sub()(和sub())进行搜索和替换
有两种函数/方法用于完成搜索和代替的功能:sub() 和subn()。二者几乎一样,都是将某字符串中所有匹配正则表达式模式的部分进行替换。用来替换的部分通常是一个字符串,但也可能是
一个函数,该函数返回一个用来替换的字符串。subn()和sub()一样,但它还返回一个表示替换次数的数字,替换后的字符串和表示替换次数的数字作为一个元组的元素返回。
import re
m=re.sub('X','Mr. Simth','attn: X\n\nDear X,\n')
print m
m1=re.subn('X', 'Mr. Simth', 'attn: X\n\nDear X,\n')
print m1
15.3.13 用split()分割(分割模式)
re模块和正则表达式对象的方法split()与字符串的split()方法相似
re模块和正则表达式对象的方法split()与字符串的split()方法相似,前者是根据正则表达式模式分离字符串,后者是根据固定的字符串分离,因此与后者相比,显著提升了字符分割的能力
同时可以设置最大分割次数
re.split(':', 'str1:str2:str3')
15.4正则表达式示例
·
from random import randint,choice
from string import lowercase
from sys import maxint
from time import ctime
doms = ('com','edu','net','org','gov')
for i in range(randint(5,10)):
dtint = randint(0,maxint-1)
dtstr = ctime(dtint)
shorter=randint(4,7)
em = ''
for j in range(shorter):
em += choice(lowercase)
longer = randint(shorter,12)
dn = ''
for j in range (longer):
dn += choice(lowercase)
print '%s::%s@%s.%s::%d-%d-%d' %(dtstr, em,dn,choice(doms),dtint,shorter,longer)
15.4.2 搜索与匹配的比较,贪婪匹配
“.+”表示任意个字符集,会默认抓取满足匹配的最长字符串
解决“非贪婪”的操作符“?”,这个操作符可以用在“*”,“+”,或”?“的后面,它的作用是要求正则表达式引擎匹配的字符越少越好
15.5 练习
15-1识别下列字符串:“bat,” “bit,” “but,” “hat,” “hit,” 或 “hut”
import re
patt='bat|bit||but|hat|hit|hut|'
m = re.match(patt,'bit')
print m.group()
15-2 匹配用一个空格分隔的任意一对单词,比如,名和姓。
import re
patt='\w+\s\w+'
m = re.match(patt,'lin feng')
print m.group()
15–3. 匹配用一个逗号和一个空格分开的一个单词和一个字母。例如,英文人名中的姓和名的首字母。
import re
patt='\w+\,\s[a-zA-Z]'
m = re.match(patt,'dai, f')
print m.group()
15-4 匹配所有合法的Python 标识符。
import re
patt='([A-Za-z_]+)([A-Za-z0-9_]*)'
m = re.match(patt,'d_333f')
print m.group()
15-5 请根据您(读者)本地关于地址的格式写法匹配一个街道地址(你写出的正则表达式要尽可能通用以匹配任意数目的表示街道名字的单词,包括类型指示)。比如,美国的街道地址使用这样的格式:1180 Bordeaux Drive. 使你写的正则表达式尽可能通用,要求能够匹配多个单词的街道名字,如:3120 De la Cruz Boulevard.
import re
patt='\d+(\s[a-zA-Z]+)+'
m = re.match(patt,'3120 De la Cruz Boulevard')
print m.group()
15–6. 匹配简单的以“www.”开头,以“.com”作结尾的Web 域名,例如:www.yahoo.com. 附加题:使你写的正则表达式还支持其他顶级域名:.edu, .net 等,比如:www.ucsc.edu.
import re
patt='(www\.)(\w+)\.[com|edu|net]*'
m = re.match(patt,'www.yahoo.com')
print m.group()
15-7匹配全体Python整数的字符串表示形式的集合
patt ='\d+'
15-8 匹配全体Python长整数的字符串表示形式的集合
patt='\d+[1L]'
15-9 匹配全体Python浮点数的字符串表示形式的集合
patt = '\d+\.\d*'
15-10 匹配全体Python复数的字符串表示形式的集合
import re
patt='(\d*\.?\d*)([+-])?(\d*\.?\d*)j'
m = re.match(patt,'2.5+4j')
print m.group()
15-11 匹配所有合法的电子邮件地址(先写出一个限制比较宽松的正则表达式,然后尽可能加强限制条件,但要保证功能的正确性)。
import re
patt='\w+@\w+.com'
m = re.match(patt,'finndai@163.com')
print m.group()
15-12.匹配所有合法的Web网站地址(URLs)(先写出一个限制比较宽松的正则表达式,然后尽可能加强限制条件,但要保证功能的正确性)。
import re
patt='www\.\w+\.\w+'
m = re.match(patt,'www.google.com')
print m.group()
15-13type(). type()内建函数返回一个对象类型,此对象显示为Python 的字符串形式,
如下所示:
>>> type(0)
<type 'int'>
>>> type(.34)
<type 'float'>
>>> type(dir)
<type 'builtin_function_or_method'>
请写一个正则表达式,能从这个字符串中提取出类型的名字。
import re
ret = type(dir)
patt="<type '(\w+)'>"
m = re.search(patt,str(ret))
print m.group(1)
15-14正则表达式。在15.2小节里,我们给出一个匹配由一位或两位数字代表一月到九月的字符串形式(“0?[1-9]”)。请写出一个正则表达式表示标准日历上其它的三个月(十月、十一月、十二月)。
patt='1[0-2]'
15–15. 正则表达式。在15.2 小节里,我们给出一个匹配信用卡卡号的模式:(“[0-9]{15,16}”).但这个模式不允许用连字符号分割信用卡卡号中的数字。请写出一个允许使用连字符的正则表达式,但要求连字符必须出现在正确的位置。例如,15 位的信用卡卡号的格式是4-6-5,表示四个数字,一个连字符,后面接六个数字、一个连字符,最后是五个数字。16 位的信用卡卡号的格式是4-4-4-4,数位不足时,添0 补位。附加题:有一个用于确定某个信用卡卡号是否合法的算法。请写一段代码,它不但能识别格式正确的信用卡卡号,还能验证它的有效性。
import re
patt='[0-9]{4}-[0-9]{6}-[0-9]{5}|[0-9]{4}-[0-9]{4}-[0-9]{4}'
m = re.match(patt,'6555-4444-3333')
print m.group()
15–16. 修改脚本gendata.py 的代码,使数据直接写入文件redata.txt 中,而不是输出到屏幕上。
from random import randint,choice
from string import lowercase
from sys import maxint
from time import ctime
doms = ('com','edu','net','org','gov')
f=open('redata.txt','w+')
for i in range(randint(5,10)):
dtint = randint(0,maxint-1)
dtstr = ctime(dtint)
shorter=randint(4,7)
em = ''
for j in range(shorter):
em += choice(lowercase)
longer = randint(shorter,12)
dn = ''
for j in range (longer):
dn += choice(lowercase)
f.write ('%s::%s@%s.%s::%d-%d-%d' %(dtstr, em,dn,choice(doms),dtint,shorter,longer))
f.close()
15-17. 统计生成的redata.txt 文件中,星期中的每一天出现的次数(或统计各月份出现的次数)。
import re
patt='(Mon|Tue|Wed|Thu|Fri|Sat|Sun)+'
num=[]
with open ('redata.txt') as f:
for item in f:
num += re.findall(patt,item)
print "Mon:%d" %num.count("Mon")
print "Tue:%d" %num.count("Tue")
print "Wed:%d" %num.count("Wed")
print "Thu:%d" %num.count("Thu")
print "Fri:%d" %num.count("Fri")
print "Sat:%d" %num.count("Sat")
print "Sun:%d" %num.count("Sun")
15-19提取出每行中完整的时间戳字段。
import re
patt ='[a-zA-Z]+ [a-zA-Z]+ \d+ \d+:\d+:\d+ \d+'
num = []
with open ('redata.txt') as f:
for item in f:
num += re.findall(patt,item)
print num
15-20 提取出每行中完整的电子邮件地址。
import re
patt ='\w+@\w+\.[com|edu|net|org|gov]+'
num = []
with open ('redata.txt') as f:
for item in f:
num += re.findall(patt,item)
print num
15.21 只提取出时间戳字段中的月份。
import re
patt ='[a-zA-Z]+ [a-zA-Z]+ \d+ \d+:\d+:\d+ \d+'
num = []
with open ('redata.txt') as f:
for item in f:
num += re.findall(patt,item)
for item in num:
print item.split(' ')[1]
15-22. 只提取出时间戳字段中的年份。
import re
patt ='[a-zA-Z]+ [a-zA-Z]+ \d+ \d+:\d+:\d+ \d+'
num = []
with open ('redata.txt') as f:
for item in f:
num += re.findall(patt,item)
for item in num:
print item.split(' ')[4]
15-23只提取出时间戳字段中的值(格式:HH:MM:SS)。
import re
patt ='[a-zA-Z]+ [a-zA-Z]+ \d+ \d+:\d+:\d+ \d+'
num = []
with open ('redata.txt') as f:
for item in f:
num += re.findall(patt,item)
for item in num:
print item.split(' ')[3]
15-24 只从电子邮件地址中提取出登录名和域名(包括主域名和顶级域名,二者连在一起)。
import re
patt ='\w+@\w+'
num = []
with open ('redata.txt') as f:
for item in f:
num += re.findall(patt,item)
print num
for item in num:
print item.split('@')
15-25 只从电子邮件地址中提取出登录名和域名(包括主域名和顶级域名,二者分别提取)。
import re
patt ='\w+@\w+'
num = []
with open ('redata.txt') as f:
for item in f:
num += re.findall(patt,item)
print num
for item in num:
print item.split('@')[0],item.split('@')[1]
15–26. 将每行中的电子邮件地址替换为你自己的电子邮件地址。
import re
patt ='\w+@\w+\.\w+'
num = []
with open ('redata.txt') as f:
for item in f:
num.append(re.sub(re.findall(patt,item)[0],'finndai@163.com',item))
with open ('redata.txt','w') as f:
for item in num:
f.write(item)
with open("redata.txt") as f:
for line in f:
print line
15-28区号(第一组的三个数字和它后面的连字符)是可选的,即,你写的正则表达式对800-555-1212 和555-1212 都可以匹配。
import re
patt ='(\d{3}-)*\d{3}-\d{4}'
m = re.match(patt,'800-555-1212')
print m.group()
15-29 区号中可以包含圆括号或连字符,而且它们是可选的,就是说你写的正则表达式可以匹配800-555-1212、555-1212或(800)555-1212
写的有问题。。。