**正则表达式(Regular expressions)**提供一种方便灵活的方法来搜索、(复杂的)匹配文本中的字符串格式。单一的表达式,一般曾称作regrex,是根据“正则表达式语言”编译的一串字符。Python内置的re模块
负责正则表达式的应用执行;我下边就举几个例子。
正则表达式的应用完全可以单独列出来大书特书的,但是这超出了本书的范围。其实在网上或者其他书籍中,都有大量优秀的正则表达式教程和参考资料。
re模块
的功能可以划分成3部分:pattern matching格式匹配、substitution替换和spliting分割。自然它们都是相关联的;一个正则表达式定义了在文本中搜索的模板,接下来可以进行各种操作。一起先看个简单的例子:
假设我们想分割一句含有不同个数空格符的语句。来代表一个或多个空格符的正则表达式是\s+
:
>>> import re
>>> text = 'foo bar\t baz \tqiyihn'
>>> text_1 = re.split('\s+, text)
>>> print(text_1)
['foo', 'bar', 'baz', 'qux']
>>>
>>> # b = text.split(' ')
>>> # print(b)
# ['foo', 'bar\t', 'baz', '', '', '', '', '\tqiyihn']
>>>
当你使用 re.split('\s+', text)
的时候,先conpiled被编译的是正则表达式,接着才是对设定的文本应用分割命令。这个例子可以用re.compile
做成两步:
>>> regex = re.compile('\s+')
>>> print(regex.split(text)
['foo', 'bar', 'baz', 'qux']
>>>
如果你想得到文本的正则表达式模板,而不是分割文本,你可以使用findall
方法:
>>> print(regex.findall(text))
>>> [' ', '\t ', ' \t']
斜杠“\”在python中为转义符,为了使用单个斜杠符号,需要“\”双斜杠(把转义符转义)。为了避免失误,我们可以前缀r,比如
r'C:\x'
来替换'C:\\x'
。
使用re.compile
的优势在于,当你需要对多个文本调用相同的命令时,可以重复调用,即可以节省CPU循环。
match
和search
和findall
功能相似。只不过search
仅返回满足条件的第一个结果,而findall
返回所有满足条件的结果。更严格地,match
仅在语句的开头进行匹配。让我们来看一个更重要的例子:
>>> text = """Dave [email protected]
Steve [email protected]
Rob [email protected]
Ryan [email protected]
"""
>>> pattern = r'[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}'
>>> # re.IGNORECASE makes the regex case-insensitive
>>> regex = re.compile(pattern, flags=re.IGNORECASE)
使用findall
返回的是邮箱地址列表:
>>> print(regex.findall(text))
['[email protected]', '[email protected]', '[email protected]', '[email protected]']
>>>
使用search
返回文本中第一个电子邮件地址的特殊匹配对象,随后的操作也只能告诉我们模板在字符串语句中的起始位置和结束位置。
>>> m =regex.search(text)
>>> print(m)
<_sre.SRE_Match object; span=(5, 20), match='[email protected]'>
>>> print(text[m.start():m.end()])
[email protected]
regex.match
返回None
,因为它只会返回在语句的开头有没有满足模板的匹配。
>>> print(regex.match(text))
None
>>>
此外,sub
则会用新的string替换了满足匹配样式的语句部分,并返回使用新string的结果:
>>> print(regex.sub('REDACTED', text))
Dave REDACTED
Steve REDACTED
Rob REDACTED
Ryan REDACTED
假如你想找到邮箱地址,并且把它分“user_name”“domain_name”和“domain_suffix”,那么给模板各个部分加上括号就好了:
>>> pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})'
>>> regex = re.compile(pattern, flags=re.INGORECASE)
应用了上边这个新模板之后,就能得到一个tuple:
>>> m = regex.match('[email protected]')
>>> print(m.groups())
('wesm', 'bright', 'net')
findall
返回一个满足模板条件的tuple列表:
>>> print(regex.findall(text))
[('dave', 'google', 'com'), ('steve', 'gmaile', 'com'), ('rob', 'mail', 'com'), ('ryan', 'yahoo', 'com')]
sub
也能在心模板下调用,比如使用代表符号 \1
、\2
,分别代表第一个满足模板样式和第二个满足模板的结果:
>>> print(regex.sub(r'Username: \1, Domain: \2, Suffix: \3', text))
Dave Username: dave, Domain: google, Suffix: com
Steve Username: steve, Domain: gmaile, Suffix: com
Rob Username: rob, Domain: mail, Suffix: com
Ryan Username: ryan, Domain: yahoo, Suffix: com
Python里边还有许多的正则表达函数,大多数都超出了本书的范围,从下图你可以看到简单的介绍:
Augement | Description |
---|---|
findall | 返回满足模板条件的所有无重复的结果,以列表输出 |
finditer | 和findall相似,但是只返回一个迭代器 |
match | 在语句字符串的开头匹配模板,并可选择将模板部分划分为组;如果有满足模板的内容,则返回该内容,否则返回None . |
search | 扫描整个用于匹配模板的字符串语句;返回一个满足模板条件样式的结果;和match返回字符串开头的结果不同,它返回的结果可能来自于字符串的任意的位置。 |
split | 用设定的分隔符把字符串进行分割 |
sub,subn | 把字符串中所有或者前n个(subn)满足模板匹配条件的部分,替换为指定的东西;使用 \1...\n …代表满足匹配条件的第一到第n个的位置。 |
注:
Q: 为什么 \1...\n表示第1到第n个,python不是以0开始计数吗?
A: 结合python中的group()p方法来看:
group()在正则表达式中用于获取分段截获的字符串,解释如下代码(代码来自网络):
import re
a = "123abc456"
print re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(0) #运行结果:123abc456,返回整体
print re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(1) #运行结果:123
print re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(2) #运行结果:abc
print re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(3) #运行结果:456
可以看出,正则表达式按照数字-字母-数字的顺序来获取相应字符串,那么分别就是“数字(group(1))–字母(group(2))–数字(group(3))”的对应关系,
其中,group(0)和group()效果相同,均为获取取得的字符串整体。