Python通过标准库中的re模块来支持正则表达式。
1. 正则表达式的基本符号
符号 | 说明 |
---|---|
| | 表示择一匹配的管道符号(|),也就是键盘上的竖线,表示一个“从多个模式中选择其一”的操作。 |
. | 点号或者句点(.)符号匹配除了换行符\n 以外的任何字符。 |
^ | 脱字符(^)或者特殊字符\A匹配字符串的开始位置,美元符号($)或者\Z匹配字符串的末尾位置。 |
[] | 方括号([])匹配一对方括号中包含的任何字符。 |
- | 连字符(-)连接,用于指定一个字符的范围,例如,A-Z、a-z 或者0-9 |
^[] | 脱字符紧跟在左方括号后面,这个符号就表示不匹配给定字符集中的任何一个字符。例如:[^aeiou]匹配一个非元音字符 |
*,+,? | 星号或者星号操作符(*)将匹配其左边的正则表达式出现零次或者多次;。加号(+)操作符将匹配一次或者多次出现的正则表达式;问号(?)操作符将匹配零次或者一次出现的正则表达式。 |
{N} | {N}最终精确地匹配前面的正则表达式N次,{M,N}将匹配M~N 次出现。 |
\d,\w,\s | 简单地使用d表示匹配任何十进制数字,字符(\w)能够用于表示全部字母数字的字符集,\s可以用来表示空格字符. |
() | 圆括号可以对正则表达式进行分组;匹配子组。 |
扩展表示法 | 扩展表示法,它们是以问号开始(?…)。 |
2. python实现正则表达式
本文将介绍两个主要的方法——match()和search(),以及compile()函数。
compile(pattern,flags = 0) 使用任何可选的标记来编译正则表达式的模式,然后返回一个正则表达式对象
match(pattern,string,flags=0) 尝试使用带有可选的标记的正则表达式的模式来匹配字符串。如果匹配成功,就返回匹配对象;如果失败,就返回None.
search(pattern,string,flags=0) 使用可选标记搜索字符串中第一次出现的正则表达式模式。如果匹配成功,则返回匹配对象;如果失败,则返回None
在模式匹配发生之前,正则表达式模式必须编译成正则表达式对象。由于正则表达式在执行过程中将进行多次比较操作,因此强烈建议使用预编译。而且,既然正则表达式的编译是必需的,那么使用预编译来提升执行性能无疑是明智之举。re.compile()能够提供此功能。compile()方法编译后得到正则表达式对象。
除了正则表达式对象之外,还有另一个对象类型:匹配对象。这些是成功调用match()或者search()返回的对象。匹配对象有两个主要的方法:group()和groups()
group()要么返回整个匹配对象,要么根据要求返回特定子组。groups()则仅返回一个包含唯一或者全部子组的元组。如果没有子组的要求,那么当group()仍然返回整个匹配时,groups()
返回一个空元组。
match()函数试图从字符串的起始部分对模式进行匹配。如果匹配成功,就返回一个匹配对象;如果匹配失败,就返回None,匹配对象的group()方法能够用于显示那个成功的匹配。
search()的工作方式与match()完全一致,不同之处在于search()会用它的字符串参数,在任意位置对给定正则表达式模式搜索第一次出现的匹配情况。正则表达式对象方法使用可选的pos和endpos 参数来指定目标字符串的搜索范围。
from re import match
from re import search
from re import compile
import re
import os
def match_search_test():
# match是从字符串的起始位置开始匹配(正常匹配)
mt_str0 = match('foo', 'foo the distance link')
if mt_str0:
print 'foo the distance link 的匹配结果:%s' % (mt_str0.group())
# match是从字符串的起始位置开始匹配(无法匹配)
mt_str = match('foo', 'g foo the distance link')
if mt_str:
print 'g foo the distance link 的匹配结果:%s' % mt_str.group()
m_str1 = search('foo', 'g foo the distance link')
if m_str1:
print 'g foo the distance link 的搜索结果:%s' % m_str1.group()
# 预编译正则表达式pattern
regex2 = compile('foo')
m_str2 = regex2.search(string='g foo the distance link', pos=0, endpos=10)
print 'g foo the distance link 的在1到10的搜索结果:%s' % m_str2.group()
# 匹配多个字符串
regex3 = compile('bat|bit|bet') # 正则表达式对象
m_str3 = regex3.search('fly!my bat')
if m_str3:
print 'fly!my bat的搜索结果:%s' % m_str3.group()
# 匹配任何单个字符 点号(.)不能匹配一个换行符\n 或者非字符
anyend = '.end'
m_str4 = match(anyend, '.end')
print '.end的匹配结果:%s' % m_str4.group()
m_str5 = match(anyend, '\nend')
print '\nend的匹配结果:%s' % str(m_str5 is not None)
m_str6 = match(anyend, 'end')
print 'end的匹配结果:%s' % str(m_str6 is not None)
# 创建字符集[ ]
regex7 = compile('[cr][23][dp][o2]')
m_str7 = regex7.match('c3po')
print 'c3po的匹配结果:%s' % m_str7.group()
# 重复、特殊字符和分组
regex8 = compile(r'(\w)+-(\d)+')
m_str8 = regex8.match('abd-123')
print 'abd-123的匹配结果:%s' % m_str8.group()
m_str9 = match('\w+@(\w+\.)(\w+\.)\w+\.com','[email protected]')
print '[email protected]的匹配结果:%s' % m_str9.group()
# 当正则表达式有圆括号括起来的部分时,就称为子组。 按顺序1,2,3,...子组。可以使用group(n) 来返回每个子组匹配上的字符串。
# 同时,可以使用groups方法,返回匹配这些子组的字符串所组成的元组列表。
print m_str9.group(1),m_str9.group(2),m_str9.groups()
# 匹配字符串的起始和结尾以及单词边界 字符串前加r,正则表达式,不加无法匹配。
m_str10 = search(r'\bthe', 'bit the dog')
print 'bit the dog的搜索结果:%s' % m_str10.group()
findall()查询字符串中某个正则表达式模式全部的非重复出现情况。findall()总是返回一个列表。如果findall()没有找到匹配的部分,就返回一个空列表,但如果匹配成功,列表将包含所有成功的匹配部分(从左向右按出现顺序排列)。
finditer()与findall()函数类似但是更节省内存。
有两个函数/方法用于实现搜索和替换功能:sub()和subn()。两者几乎一样,都是将某字符串中所有匹配正则表达式的部分进行某种形式的替换。。subn()和sub()一样,但subn()还返回一个表示替换的总数,替换后的字符串和表示替换总数的数字一起作为一个拥有两个元素的元组返回。
如果给定分隔符不是使用特殊符号来匹配多重模式的正则表达式,那么re.split()与str.split()的工作方式相同,re.split()可以解决正则表达式匹配的问题。
def findall_finditer():
s = 'This and That and. That and This'
m_str1 = re.findall(r'(Th\w+) and (Th\w+)', s, re.I)
print m_str1
m_str2 = re.sub(r'GD|TC|BD', 'MT', 'The best Map provider is BD, TC and GD!')
print m_str2
m_str3 = re.subn(r'GD|TC|BD', 'MT', 'The best Map provider is BD, TC and GD!')
print m_str3
#\N 可以直接替换分组, 第二个参数表示将第2个分组和第1个分组调换位置
m_str4 = re.sub(r'(\d{1,2})/(\d{1,2})/(\d{4})', r'\2/\1/\3', '9/18/1987')
print m_str4
上边的程序例子中,部分在正则表达式前添加了r,例如r'\s+',被称为python原始字符串。使用这个的原因是:ASCII字符和正则表达式的特殊字符之间存在冲突,例如\b 表示ASCII 字符的退格符,但是\b同时也是一个正则表达式的特殊符号,表示匹配一个单词的边界。那么不加r时,要正确表达,就需要写成'\b'来转义\符号,使用r''表达方式则更简单了,不需要转义。
3.简单例子
3.1需求描述
这一部分,通过正则表达式完成一个十分简单的需求,在输入who命令时,返回类似如下结果:
natty console Apr 13 21:58
natty ttys000 Apr 13 22:10
我们需要将结果按照登录名、用户登录的终端类型、用户登录的时间和地点来分列,最后打印输出一个列表。
3.2 分析和代码
如果使用简单的空格分割列的话,登陆时间项中也有空格,会造成结果错误,可以使用正则表达式。另外,执行系统命令,可以使用os.popen()来完成。str.rstrip()可以用来去除尾部的\n。分割列的是2个及以上的空格,所以程序如下:
# 按列格式化输出 who命令的执行结果:
def rewho():
# os.popen()执行一个命令,并返回一个文件对象
with os.popen('who') as f:
for line in f.readlines():
print re.split(r'\s\s+', line.strip())
输出结果:
['natty', 'console', 'Apr 13 21:58']
['natty', 'ttys000', 'Apr 13 22:10']
4.练习数据集生成
需求: 生成拥有三个字段的字符串,由一对冒号或者一对双冒号分隔。第一个字段是随机(32 位)整数,该整数将被转换为一个日期。下一个字段是一个随机生成的电子邮件地址。最后一个字段是一个由单横线(-)分隔的整数集。
程序如下:
import random
from time import ctime
from string import ascii_lowercase
tlds = ('com', 'edu', 'org', 'net', 'gov')
if __name__ == '__main__':
# random.randrange(5, 11) 生成 >=5 并 <11的一个随机数 递增基数是默认值1
# xrange() 函数用法与 range 完全相同,所不同的是生成的不是一个数组,而是一个生成器。
with open('datafiles/regex_data_sample_gen.txt', 'w') as f:
for i in xrange(random.randrange(7, 15)):
# 如果在32位机器上,maxint就是-2**31~2**31-1,如果在64位机器上,maxint就是-2**63~2**63-1 [dtint = random.randrange(maxint)]
# ctime()将一个int类型的unix时间戳转化为时间字符串。
dint = random.randrange(2**31-1)
dtstr = ctime(dint)
llen = random.randrange(4, 8)
dlen = random.randrange(llen, 13)
login_list, dom_list = [], []
# string.ascii_lowercase 是字母表中拥有26个小写字母的序列集合
for j in range(llen):
login_list.append(random.choice(list(ascii_lowercase)))
for j in range(dlen):
dom_list.append(random.choice(list(ascii_lowercase)))
login = ''.join(login_list)
dom = ''.join(dom_list)
# choice() 方法返回一个列表,元组或字符串的随机项。 例如, choice([1, 2, 3, 5, 9]) : 2
# join() 方法用于将序列中的元素以指定的字符连接生成一个新的字符串。 例如 str = 'Accenture' '|+|'.join(list(str))
# 得到:A|+|c|+|c|+|e|+|n|+|t|+|u|+|r|+|e
dom_name = random.choice(tlds)
f.write(
"%s::%s@%s.%s::%d-%d-%d\n" % (dtstr, login, dom, dom_name, dint, llen, dlen)
)
f.close()