Python-正则表达式
正则表达式(简称为 regex)是一些由字符和特殊符号组成的字符串,
它们描述了模式的重复或者表述多个字符
于是正则表达式能按照某种模式匹配一系列有相似特征的字符串
便捷的操作文本或者数据 。
搜索和匹配的比较 |
---|
在 Python 术语中,主要有两种方法完成模式匹配:“搜索”( searching),即在字符串任意部 分中搜索匹配的模式;而“匹配”( matching)是指判断一个字符串能否从起始处全部或者 部分地匹配某个模式。搜索通过 search()函数或方法来实现,而匹配通过调用 match()函数 或方法实现。总之,当涉及模式时,全部使用术语“匹配”;我们按照 Python 如何完成模 式匹配的方式来区分“搜索”和“匹配”。 |
简单的正则匹配
正则表达式模式 | 匹配的字符串 |
---|---|
foo | foo |
Python | Python |
abc123 | abc123 |
特殊符号和字符
常见的特殊符号和字符,即所谓的元字符,正是它给予正则表达式强大的功能和灵活性。
本节将介绍最常见的特殊符号和字符,即所谓的元字符,正是它给予正则表达式强大的功能和灵活性。
常见正则表达式符号和特殊字符
表 示 法 | 描 述 | 正则表达式示例 |
---|---|---|
符号 | ||
literal | 匹配文本字符串的字面值 literal | foo |
re1|re2 | 匹配正则表达式 re1 或者 re2 | foo|bar |
. | 匹配任何字符(除了\n 之外) | b.b |
^ | 匹配字符串起始部分 | ^Dear |
$ | 匹配字符串终止部分 | /bin/*sh$ |
* | 匹配 0 次或者多次前面出现的正则表达式 | [A-Za-z0-9]* |
+ | 匹配 1 次或者多次前面出现的正则表达式 | [a-z]+.com |
? | 匹配 0 次或者 1 次前面出现的正则表达式 | goo? |
{N} | 匹配 N 次前面出现的正则表达式 | [0-9]{3} |
{M,N} | 匹配 M~N 次前面出现的正则表达式 | [0-9]{5,9} |
[…] | 匹配来自字符集的任意单一字符 | [aeiou] |
[..x-y..] | 匹配 x~y 范围中的任意单一字符 | [0-9], [A-Za-z] |
[^…] | 不匹配此字符集中出现的任何一个字符,包括某一范围的字符(如果在此字符集中出现) | [^aeiou], A-Za-z0-9 |
(*|+|?|{})? | 用于匹配上面频繁出现/重复出现符号的非贪婪版本(*、 +、 ?、 {}) | .*?[a-z] |
(…) | 匹配封闭的正则表达式,然后另存为子组 | ([0-9]{3})?,f(oo|u)bar |
特殊字符 | ||
\d | 匹配任何十进制数字,与[0-9]一致( \D 与\d 相反,不匹配任何非数值型的数字) | data\d+.txt |
\w | 匹配任何字母数字字符,与[A-Za-z0-9_]相同( \W 与之相反) | [A-Za-z_]\w+ |
\s | 匹配任何空格字符,与[\n\t\r\v\f]相同( \S 与之相反) | of\sthe |
\b | 匹配任何单词边界( \B 与之相反) | \bThe\b |
\N | 匹配已保存的子组 N(参见上面的(…)) | price: \16 |
\c | 逐字匹配任何特殊字符 c(即,仅按照字面意义匹配,不匹配特殊含义) | ., \, * |
\A(\Z) | 匹配字符串的起始(结束)( 另见上面介绍的^和$) | \ADear |
扩展表示法 | ||
(?iLmsux) | 在正则表达式中嵌入一个或者多个特殊“ 标记” 参数(或者通过函数/方法) | ( ?x),(? im) |
(?:…) | 表示一个匹配不用保存的分组 | (?:\w+.)* |
(?P<name>…) | 像一个仅由 name 标识而不是数字 ID 标识的正则分组匹配 | (?P<data>) |
(?P=name) | 在同一字符串中匹配由(?P<name)分组的之前文本 | (?P=data) |
(?#…) | 表示注释,所有内容都被忽略 | (?#comment) |
(?=…) | 匹配条件是如果…出现在之后的位置,而不使用输入字符串;称作正向前视断言 | (?=.com) |
(?!…) | 匹配条件是如果…不出现在之后的位置,而不使用输入字符串;称作负向前视断言 | (?!.net) |
(?<=…) | 匹配条件是如果…出现在之前的位置,而不使用输入字符串;称作正向后视断言 | (?<=800-) |
(?<!…) | 匹配条件是如果…不出现在之前的位置,而不使用输入字符串;称作负向后视断言 | (?<!192.168.) |
(?(id | name)Y | N ) | 如果分组所提供的 id 或者 name(名称)存在,就返回正则表达式的条件匹配 Y,如 果不存在,就返回 N;|N 是可选项 | (?(1)Y | N) |
使用择一匹配符号匹配多个正则表达式模式
表示择一匹配的管道符号( |),也就是键盘上的竖线,表示一个“从多个模式中选择其一”的操作。
它用于分割不同的正则表达式。
正则表达式模式 | 匹配的字符串 |
---|---|
at | home | at、 home |
r2d2 | c3po | r2d2、 c3po |
bat | bet | bit | bat、 bet、 bit |
匹配任意单个字符
点号或者句点( .) 符号匹配除了换行符\n 以外的任何字符
(Python 正则表达式有一个编译标记[S 或者 DOTALL],该标记能够推翻这个限制,使点号能够匹配换行符)。
无论字母、数字、 空格(并不包括“\n”换行符)、可打印字符、 不可打印字符,
还是一个符号,使用点号都能够匹配它们。
正则表达式模式 | 匹配的字符串 |
---|---|
f.o | 匹配在字母“f”和“o”之间的任意一个字符;例如 fao、 f9o、 f#o 等 |
.. | 任意两个字符 |
.end | 匹配在字符串 end 之前的任意一个字符 |
要显式匹配一个句点符号本身,必须使用反斜线转义句点符号的功能,例如“.”
从字符串起始或者结尾或者单词边界匹配
还有些符号和相关的特殊字符用于在字符串的起始和结尾部分指定用于搜索的模式。如
果要匹配字符串的开始位置,就必须使用脱字符(^)或者特殊字符\A
后者主要用于那些没有脱字符的键盘(例如,某些国际键盘)。
同样,美元符号($)或者\Z将用于匹配字符串的末尾位置。
正则表达式模式 | 匹配的字符串 |
---|---|
^From | 任何以 From 作为起始的字符串 |
/bin/tcsh$ | 任何以/bin/tcsh 作为结尾的字符串 |
^Subject: hi$ | 任何由单独的字符串 Subject: hi 构成的字符串 |
再次说明,如果想要逐字匹配这些字符中的任何一个(或者全部),就必须使用反斜线进行转义。
例如,如果你想要匹配任何以美元符号结尾的字符串,一个可行的正则表达式方案,就是使用模式.*$$
特殊字符\b 和\B 可以用来匹配字符边界。
而两者的区别在于\b 将用于匹配一个单词的边界,这意味着如果一个模式必须位于单词的起始部分,
就不管该单词前面(单词位于字符串中间)是否有任何字符(单词位于行首)。
同样, \B 将匹配出现在一个单词中间的模式(即,不是单词边界)。
正则表达式模式 | 匹配的字符串 |
---|---|
the | 任何包含 the 的字符串 |
\bthe | 任何以 the 开始的字符串 |
\bthe\b | 仅仅匹配单词 the |
\Bthe | 任何包含但并不以 the 作为起始的字符串 |
创建字符集
尽管句点可以用于匹配任意符号,但某些时候,可能想要匹配某些特定字符。
正因如此,发明了方括号。该正则表达式能够匹配一对方括号中包含的任何字符。
正则表达式模式 | 匹配的字符串 |
---|---|
b[aeiu]t | bat、 bet、 bit、 but |
[c] [r] [d] [p] | 一个包含四个字符的字符串,第一个字符是“c”或“r”,然后是“2”或“3”,后面 是“d”或“p”,最后要么是“o”要么是“2”。例如, c2do、 r3p2、 r2d2、 c3po 等 |
关于
crdp这个正则表达式有一点需要说明:如果仅允许“r2d2”或者“c3po”作为有效字符串,
就需要更严格限定的正则表达式。
因为方括号仅仅表示逻辑或的功能,所以使用方括号并不能实现这一限定要求。
唯一的方案就是使用择一匹配,例如,r2d2|c3po。
限定范围和否定
除了单字符以外,字符集还支持匹配指定的字符范围。方括号中两个符号中间用连字符 (-)连接,用于指定一个字符的范围;
例如, A-Z、 a-z 或者 0-9 分别用于表示大写字母、 小写字母和数值数字。
正则表达式模式 | 匹配的字符串 |
---|---|
z.[0-9] | 字母“z”后面跟着任何一个字符,然后跟着一个数字 |
[r-u] [env-y] [us] | 字母“r”、“s”、“t”或者“u”后面跟着“e”、“n”、“v”、“w”、“x”或者“y”,然后跟着“u”或者“s” |
[^aeiou] | 一个非元音字符(练习:为什么我们说“非元音”而不是“辅音”?) |
[^\t\n] | 不匹配制表符或者\n |
[“-a] | 在一个 ASCII 系统中,所有字符都位于“”和“a”之间,即 34~97 之间 |
使用闭包操作符实现存在性和频数匹配
本节介绍最常用的正则表达式符号, 即特殊符号*、 +和?,
所有这些都可以用于匹配一个、 多个或者没有出现的字符串模式。
星号或者星号操作符(*)将匹配其左边的正则表达式出现零次或者多次的情况
(在计算机编程语言和编译原理中,该操作称为 Kleene 闭包)
加号(+)操作符将匹配一次或者多次出现的正则表达式(也叫做正闭包操作符)
问号(?)操作符将匹配零次或者一次出现的正则表达式。
还有大括号操作符( {}),里面或者是单个值或者是一对由逗号分隔的值。
这将最终精确地匹配前面的正则表达式
N 次(如果是{N})或者一定范围的次数;例如, {M, N}
将匹配 M~N 次出现。
这些符号能够由反斜线符号转义; \ *匹配星号,等等。 注意,在之前的表格中曾经多次使用问号(重载), 这意味着要么匹配 0 次,要么匹配 1次,
或者其他含义:
如果问号紧跟在任何使用闭合操作符的匹配后面, 它将直接要求正则表达式引擎匹配尽可能少的次数。
“尽可能少的次数” 是什么意思?
当模式匹配使用分组操作符时,正则表达式引擎将试图“吸收”匹配该模式的尽可能多的字符。
这通常被叫做贪婪匹配。
问号要求正则表达式引擎去“偷懒”,如果可能,就在当前的正则表达式中尽可能少地匹配字符,
留下尽可能多的字符给后面的模式(如果存在)。
非贪婪匹配是很有必要的。
正则表达式模式 | 匹配的字符串 |
---|---|
[dn]ot? | 字母“d”或者“n”,后面跟着一个“o”,然后是最多一个“t”,例如, do、 no、 dot、 not |
0?[1-9] | 任何数值数字, 它可能前置一个“0”,例如, 匹配一系列数(表示从 1~9 月的数值),不 管是一个还是两个数字 |
[0-9]{15,16} | 匹配 15 或者 16 个数字(例如信用卡号码) |
</?>+> | 匹配全部有效的(和无效的) HTML 标签 |
[KQRBNP] [a-h] [1-8]- [a-h] [1-8] | 在“长代数”标记法中,表示国际象棋合法的棋盘移动(仅移动,不包括吃子和将军)。 即“K”、“Q”、“R”、“B”、“N”或“P”等字母后面加上“a1”~“h8”之间的棋盘坐标。 前面的坐标表示从哪里开始走棋,后面的坐标代表走到哪个位置(棋格)上 |
表示字符集的特殊字符
我们还提到有一些特殊字符能够表示字符集。
与使用“0-9”这个范围表示十进制数相比,可以简单地使用 \d 表示匹配任何十进制数字。
另一个特殊字符(\w) 能够用于表示全部字母数字的字符集,相当于[A-Za-z0-9_]的缩写形式,
\s 可以用来表示空格字符。
这些特殊字符的大写版本表示不匹配;
例如, \D 表示任何非十进制数(与[\^0-9]相同), 等等。
使用这些缩写,可以表示如下一些更复杂的示例。
正则表达式模式 | 匹配的字符串 |
---|---|
\w+-\d+ | 一个由字母数字组成的字符串和一串由一个连字符分隔的数字 |
[A-Za-z]\w* | 第一个字符是字母;其余字符(如果存在)可以是字母或者数字(几乎等价于 Python 中的有 效标识符[参见练习]) |
\d{3}-\d{3}-\d{4} | 美国电话号码的格式,前面是区号前缀,例如 800-555-1212 |
\w+@\w+.com | 以 XXX@YYY.com格式表示的简单电子邮件地址 |
使用圆括号指定分组
现在,我们已经可以实现匹配某个字符串以及丢弃不匹配的字符串,
但有些时候,我们可能会对之前匹配成功的数据更感兴趣。
我们不仅想要知道整个字符串是否匹配我们的标准, 而且想要知道能否提取任何已经成功匹配的特定字符串或者子字符串。
答案是可以,要实现这个目标,只要用一对圆括号包裹任何正则表达式。
正则表达式模式 | 匹配的字符串 |
---|---|
\d+(.\d*)? | 表示简单浮点数的字符串;也就是说,任何十进制数字,后面可以接一个小数点和零个或 者多个十进制数字,例如“0.004”、“2”、“75.”等 |
(Mr?s?.)?[A-Z] [a-z]*[A-Za-z-]+ | 名字和姓氏,以及对名字的限制(如果有,首字母必须大写,后续字母小写),全名前可以 有可选的“Mr.”、“Mrs.”、“Ms.”或者“M.”作为称谓,以及灵活可选的姓氏,可以有多 个单词、 横线以及大写字母 |
扩展表示法
我们还没介绍过的正则表达式的最后一个方面是扩展表示法, 它们是以问号开始(?…)。
我们不会为此花费太多时间,因为它们通常用于在判断匹配之前提供标记,
实现一个前视(或者后视)匹配,或者条件检查。
尽管圆括号使用这些符号, 但是只有(?P<name>) 表述一个分组匹配。
所有其他的都没有创建一个分组。
然而,你仍然需要知道它们是什么,因为它们可能最适合用于你所需要完成的任务。
正则表达式模式 | 匹配的字符串 |
---|---|
(?:\w+.)* | 以句点作为结尾的字符串,例如“google.”、“twitter.”、“facebook.”,但是这些匹配不会保存下来 供后续的使用和数据检索 |
(?#comment) | 此处并不做匹配,只是作为注释 |
(?=.com) | 如果一个字符串后面跟着“.com”才做匹配操作,并不使用任何目标字符串 |
(?!.net) | 如果一个字符串后面不是跟着“.net”才做匹配操作 |
(?<=800-) | 如果字符串之前为“800-”才做匹配,假定为电话号码, 同样,并不使用任何输入字符串 |
(?<!192.168.) | 如果一个字符串之前不是“192.168.”才做匹配操作,假定用于过滤掉一组 C 类 IP 地址 |
(?(1)y | x) | 如果一个匹配组 (1)存在, 就与 y 匹配; 否则, 就与 x 匹配 |
正则表达式和python语言
python使用re模块支持正则表达式
主要函数:match()、search()、compile()
使用 compile()函数编译正则表达式
注意,尽管推荐预编译,但它并不是必需的。
如果需要编译,就使用编译过的方法;如果不需要编译,就使用函数。
(? F) 标记,其中 F 是一个或者多个 i(用于 re.I/IGNORECASE)、
m(用于 re.M/MULTILINE)、s(用于 re.S/DOTALL) 等。
如果想要同时使用多个,就把它们放在一起而不是使用按位或操作
例如,(?im) 可以用于同时表示 re.IGNORECASE 和 re.MULTILINE。
匹配对象以及 group()和 groups()方法
当处理正则表达式时,除了正则表达式对象之外,还有另一个对象类型:匹配对象。
这些是成功调用 match()或者 search()返回的对象。
匹配对象有两个主要的方法: group()和groups()。
group()要么返回整个匹配对象,要么根据要求返回特定子组。
groups()则仅返回一个包含唯一或者全部子组的元组。
如果没有子组的要求,那么当group()仍然返回整个匹配时, groups()返回一个空元组。
使用 match()方法匹配字符串
match()是将要介绍的第一个 re 模块函数和正则表达式对象( regex object)方法。
match()函数试图从字符串的起始部分对模式进行匹配。
如果匹配成功,就返回一个匹配对象;
如果匹配失败,就返回 None,匹配对象的 group()方法能够用于显示那个成功的匹配。
m = re.match('foo','foo') #模式匹配字符串
if m is not None: #如果匹配成功,就输出匹配内容
a=m.group()
print(a)
使用 search()在一个字符串中查找模式(搜索与匹配的对比)
search() 搜索的模式出现在一个字符串中间部分的概率 。
search()会用它的字符串参数,在任意位置对给定正则表达式模式搜索第一次出现的匹配情况。
如果搜索到成功的匹配,就会返回一个匹配对象; 否则, 返回 None。
m = re.search('foo','seafood') #使用search代替match,若是match会搜索失败
if m is not None:
a=m.group()
print(a)
匹配多个字符串
bt = 'bat|bet|bit' # 正则表达式模式: bat、 bet、 bit
m = re.match(bt, 'bat') # 'bat' 是一个匹配
if m is not None:
a=m.group()
print(a)
匹配任何单个字符
anyend = '.end'
m = re.match(anyend, 'bend') # 点号匹配 'b'
if m is not None:
a=m.group()
print(a)
创建字符集([ ])
m = re.match('[cr][23][dp][o2]', 'c3po')# 匹配 'c3po'
if m is not None:
a=m.group()
print(a)
匹配子组
m = re.match('(\w\w\w)-(\d\d\d)', 'abc-123')
m.group() # 完整匹配
'abc-123'
m.group(1) # 子组 1
'abc'
m.group(2) # 子组 2
'123'
m.groups() # 全部子组
('abc', '123')
匹配字符串的起始和结尾以及单词边界
m = re.search('^The', 'The end.') # 匹配
if m is not None: m.group()
...
'The'
m = re.search('^The', 'end. The') # 不作为起始
if m is not None: m.group()
...
m = re.search(r'\bthe', 'bite the dog') # 在边界
if m is not None: m.group()
...
'the'
m = re.search(r'\bthe', 'bitethe dog') # 有边界
if m is not None: m.group()
...
m = re.search(r'\Bthe', 'bitethe dog') # 没有边界
if m is not None: m.group()
...
'the'
使用 findall()和 finditer()查找每一次出现的位置
findall()查询字符串中某个正则表达式模式全部的非重复出现情况。
这与 search()在执行字符串搜索时类似,但与 match()和 search()的不同之处在于, findall()总是返回一个列表
如果 findall()没有找到匹配的部分,就返回一个空列表,但如果匹配成功
列表将包含所有成功的匹配部分(从左向右按出现顺序排列)
re.findall('car', 'car')
['car']
re.findall('car', 'scary')
['car']
re.findall('car', 'carry the barcardi to the car')
['car', 'car', 'car']
使用 sub()和 subn()搜索与替换
有两个函数/方法用于实现搜索和替换功能: sub()和 subn()。
两者几乎一样,都是将某字符串中所有匹配正则表达式的部分进行某种形式的替换。
用来替换的部分通常是一个字符串,但它也可能是一个函数,该函数返回一个用来替换的字符串。
subn()和 sub()一样,但 subn()还返回一个表示替换的总数,
替换后的字符串和表示替换总数的数字一起作为一个拥有两个元素的元组返回。
re.sub('X', 'Mr. Smith', 'attn: X\n\nDear X,\n')
'attn: Mr. Smith\012\012Dear Mr. Smith,\012'
re.subn('X', 'Mr. Smith', 'attn: X\n\nDear X,\n')
('attn: Mr. Smith\012\012Dear Mr. Smith,\012', 2)
print re.sub('X', 'Mr. Smith', 'attn: X\n\nDear X,\n')
attn: Mr. Smith
Dear Mr. Smith,
re.sub('[ae]', 'X', 'abcdef')
'XbcdXf'
re.subn('[ae]', 'X', 'abcdef')
('XbcdXf', 2)
在限定模式上使用 split()分隔字符串
re 模块和正则表达式的对象方法 split()对于相对应字符串的工作方式是类似的,
但是与分割一个固定字符串相比,它们基于正则表达式的模式分隔字符串,
为字符串分隔功能添加一些额外的威力。
如果你不想为每次模式的出现都分割字符串,就可以通过为 max 参数设定一个值(非零)来指定最大分割数。
如果给定分隔符不是使用特殊符号来匹配多重模式的正则表达式,
那么 re.split()与str.split()的工作方式相同,如下所示(基于单引号分割)。
re.split(':', 'str1:str2:str3')
['str1', 'str2', 'str3']
有一个更复杂的示例,例如,一个用于 Web 站点(类似于Google 或者 Yahoo! Maps)的简单解析器,
该如何实现?用户需要输入城市和州名,或者城市名加上 ZIP 编码, 还是三者同时输入?
这就需要比仅仅是普通字符串分割更强大的处理方式,具体如下
import re
DATA = ('Mountain View, CA 94040','Sunnyvale, CA', 'Los Altos, 94023', 'Cupertino 95014','Palo Alto CA',)
for datum in DATA:
print re.split(', |(?= (?:\d{5}|[A-Z]{2})) ', datum)
['Mountain View', 'CA', '94040']
['Sunnyvale', 'CA']
['Los Altos', '94023']
['Cupertino', '95014']
['Palo Alto', 'CA']
一些正则表达式示例
以 POSIX(UNIX 风格操作系统,如 Linux、 Mac OS X 等)的 who 命令的输出为例
该命令将列出所有登录当前系统中的用户信息。
创建一个名为 rewho.py 的程序,该程序读取 who 命令的输出,
然后假定将得到的输出信息存入一个名为 whoadat.txt 的文件之中
py2.x-py3.x通用
#!/usr/bin/env python
import os
from distutils.log import warn as printf
import re
with os.popen('who','r') as f:
for eachLine in f:
printf(re.split(r'\s\s+|\t',eachLine.strip()))
匹配字符串
以子组的方式来访问匹配字符串。
patt = '^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)'
m = re.match(patt, data)
m.group() # entire match
'Thu'
m.group(1) # subgroup 1
'Thu'
m.groups() # all subgroups
('Thu',)
以上两个正则表达式都是非常严格的,尤其是要求一个字符串集。
这可能在一个国际化的环境中并不能良好地工作,因为所在的环境中会使用当地的日期和缩写。
一个宽松的正则表达式将为: ^\w{3}。
该正则表达式仅仅需要一个以三个连续字母数字字符开头的字符串。
再一次,将正则表达式转换为正常的自然语言:
脱字符^表示“作为起始”, \w 表示任意单个字母数字字符, {3}表示将会有 3 个连续的正则表达式副本,
这里使用{3}来修饰正则表达式。
再一次,如果想要分组,就必须使用圆括号,例如^(\w{3})。
patt = '^(\w{3})'
m = re.match(patt, data)
if m is not None: m.group()
'Thu'
m.group(1)
'Thu'
注意, 正则表达式^(\w){3}是错误的。
当{3}在圆括号中时,先匹配三个连续的字母数字字符,然后表示为一个分组。
但是如果将{3}移到外部, 它就等效于三个连续的单个字母数字字符。
patt = '^(\w){3}'
m = re.match(patt, data)
if m is not None: m.group()
'Thu'
m.group(1)
'u'
当我们访问子组 1 时,出现字母“u”的原因是子组 1 持续被下一个字符替换。
换句话说,m.group(1)以字母“T”作为开始,然后变为“h”,最终被替换为“u”。