第13章 正则表达式
最初的正则表达式出现于理论计算机科学的自动控制理论和形式化语言理论中。在这些领域中有对计算(自动控制)的模型和对形式化语言描述与分类的研究。
程序员所用的正则表达式是指用某种模式去匹配一类具有共同特征的字符串。正则表达式主要用于处理文本,正则表达式能够使文本处理简单起来,尤其对于复杂的查找替换这样的工作,使用正则表达式会非常快的完成。流行的文本编辑器(如Emacs、Vim等)大都支持正则表达式。
13.1 正则表达式基础
维基百科所言:正则表达式又称正规表示式、正规表示法、正规表达式、规则表达式、常规表示法(英语:Regular Expression,在代码中常简写为regex、regexp或re),是计算机科学的一个概念。本节详细介绍了正则的概念和应用。
13.1.1 正则表达式概述
正则表达式使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些符合某个模式的文本。
许多程序设计语言都支持利用正则表达式进行字符串操作。例如,在Perl中就内建了一个功能强大的正则表达式引擎。正则表达式这个概念最初是由UNIX中的工具软件(例如sed和grep)普及开的。正则表达式通常缩写成“regex”,单数有regexp、regex,复数有regexps、regexes、regexen。
正则表达式主要用于快速地搜索、替换或验证具有特殊形式或格式的文本,可以应用于文本编辑、查找,也可以应用于web数据处理与分析等领域。
13.1.2 正则表达式基本元字符
元字符是正则表达式中具有特定含义的字符,在正则表达式中可以在字符串中使用元字符来匹配字符串的各种可能的情况。常用的元字符如表13.1所示。
![在这里插入图片描述](https://img-blog.csdnimg.cn/46ada904d706494ba53253042420b65f.png#pic_center)
>>> import re
>>> re.compile('\\ba.?')
re.compile('\\ba.?')
>>> re.compile(r'\ba.?')
re.compile('\\ba.?')
>>> re.compile('\\\\word')
re.compile('\\\\word')
>>> re.compile(r'\\word')
re.compile('\\\\word')
>>>
注意 正则表达式的元字符有很多,只有经常用它,才能熟练识记。
13.1.3 常用正则表达式
注意以上只是处理以13开头的手机号码,随着手机号码的不断推出,现在还有14、15、不18开头的手机号。这里为了简化正则表达式,使初学者能看懂,暂不处理其他号段的情况。
13.2 re模块
re模块是Python语言提供的处理正则表达式的标准库,在该模块中,既可以直接匹配正则表达式的基本函数,也可以通过编译正则表达式对象,并使用其方法来使用正则表达式。
13.2.1 正则匹配搜索函数
注意 区分match()函数和search()函数的功能,一个只能从第一个字符开始匹配,一个可以从要匹配的字符串的中间任一个字符进行匹配。
>>> import re
>>> s = 'Life can be good'
>>> print(re.match ('can',s))
None
>>> print(re.search('can',s))
<re.Match object; span=(5, 8), match='can'>
>>> print(re.match('1.*',s))
None
>>> print(re.match('1. *', s, re.I))
None
>>> re.findall('[a-z]{3}', s)
['ife', 'can', 'goo']
>>> re.findall('[a-z]{1,3}',s)
['ife', 'can', 'be', 'goo', 'd']
>>>
13.2.2 sub()与subn()函数
>>> import re
>>> s = 'Life can be bad'
>>> re.sub('bad', 'good', s)
'Life can be good'
>>> re.sub('bad|be', 'good', s)
'Life can good good'
>>> re.sub('bad|be', 'good', s, 1)
'Life can good bad'
>>> re.subn('bad|be', 'good', s, 1)
('Life can good bad', 1)
>>> r = re.subn('bad|be', 'good', s)
>>> print(r[0])
Life can good good
>>> print(r[1])
2
>>>
13.2.3 split()函数
re.split()函数用于分割字符串,它返回分割后的字符串列表。其函数原型分别如下。
注意 该函数返回的数据类型为列表。
13.2.4 正则表达式对象这部分演示了,但是没有保存下来
13.3 分组匹配与匹配对象使用
在正则表达式中使用组,可以将正则表达式分解成几个不同的组成部分。在完成匹配或者搜索后,可以使用分组编号访问不同部分匹配的内容。
13.3.1 分组基础
在正则表达式中以一对圆括号“()”来表示位于其中的内容属于一个分组。例如“(re)+”将匹配“rere”、“rerere”等多个“re”重复的情况。分组在匹配由不同部分组成的一个整体时非常有用。如电话号码由区号和号码组成,在正则表达式中可以使用两个分组来进行匹配:一个分组匹配区号,另一个分组匹配后边的号码。在交互式环境下演示代码如下:
13.3.2 分组扩展
除了在组中使用“(?P<组名>)”来命名组名以外,还可以使用几种以“?”开头的扩展语法,如表13.3所示:
>>> import re
>>> s = '''Life can be good;
... Life can be bad;
... Life is mostly cheerful;
... But sometimes sad.
... '''
>>> r = re.compile(r'be(?=\sgood)')
>>> m = r.search(s)
>>> m
<re.Match object; span=(9, 11), match='be'>
>>> m.span()
(9, 11)
>>> r.findall(s)
['be']
>>> r = re.compile('be')
>>> r.findall(s)
['be', 'be']
>>> r = re.compile(r'be(?!\sgood)')
>>> m = r.search(s)
>>> m
<re.Match object; span=(27, 29), match='be'>
>>> m.span()
(27, 29)
>>> r = re.compile(r'(?:can\s) be (\sgood)')
>>> m = r.search(s)
>>> m
>>> m.groups()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'groups'
>>> r = re.compile(r'(?:can\s)be(\sgood)')
>>> m = r.search(s)
>>> m
<re.Match object; span=(5, 16), match='can be good'>
>>> m.groups()
(' good',)
>>> m.groups(1)
(' good',)
>>> r = re.compile(r'(?P<first>\w)(?P=first)')
>>> r.findall(s)
['o', 'e']
>>> r = re.compile(r'(?<=can\s)b\w*\b')
>>> r.findall(s)
['be', 'be']
>>> r = re.compile(r'(?<!can\s)(?i)b\w*\b')
<stdin>:1: DeprecationWarning: Flags not at the start of the expression '(?<!can\\s)(?i)b\\w*\\b'
>>> r = re.compile(r'(?<!can\s)(?i)b\w*\b')
>>> r.findall(s)
['bad', 'But']
>>>
13.3.3 匹配对象与组的使用
Match对象实例是由正则表达式对象的match、search方法在匹配成功后返回的。Match对象有以下常用的方法和属性,用于对匹配成功的正则表达式进行处理。
group()、groups()、groupdict()方法都是处理在正则表达式中使用“()”分组的情况。不同的是,group()的返回值为字符串,当传递多个参数时其返回值为元组;groups()的返回值为元组;groupdict()的返回值为字典。其原型分别如下:
group( [group1, …])
groups( [default])
groupdict( [default])
对于group(),其参数为分组的编号。如果向group()传递多个参数,则其返回各个参数所对应的字符串组成元组。对于groups()和groupdict()一般不需要向其传递参数。
>>> s = '''Life can be dreams,
... Life can be great thoughts;
... Life can mean a person,
... Sitting in a court.'''
>>> r = re.compile('\\b(?P<first>\w+)a(\w+)\\b')
>>> m = r.search(s)
>>> m.groupdict()
{'first': 'c'}
>>> m.groups()
('c', 'n')
>>> m.groupdict()
{'first': 'c'}
>>> m = r.search(s,9)
>>> m.group()
'dreams'
>>> m.group(1)
'dre'
>>> m.group(2)
'ms'
>>> m.group(1,2)
('dre', 'ms')
>>> m.groupdict()
{'first': 'dre'}
>>> m.groups()
('dre', 'ms')
>>>
13.3.4 匹配对象与索引使用
start()、end()、span()方法返回所匹配的子字符串的索引。其原型分别如下:
start( [groupid=0])
end( [groupid=0])
span( [groupid=0])
其参数含义相同,groupid为可选参数,即分组编号。如果不向其传递参数,则返回整个子字符串的索引。start()方法返回子字符串或者组的起始位置索引。end()方法返回子字符串或者组的结束位置索引。而span()方法则以元组的形式返回以上两者。
>>> r = re.compile('\\b(?P<first>\w+)a(\w+)\\b')
>>> m = r.search(s,9)
>>> m.start()
12
>>> m.start(1)
12
>>> m.start(2)
16
>>> m.end(1)
15
>>> m.end()
18
>>> m.span()
(12, 18)
>>> m.span(2)
(16, 18)
>>>
13.4 正则表达式应用示例
正则表达式是处理文本文件的强有力工具。本节中给出一个简单地使用正则表达式处理Python程序中的函数和变量的例子。
在Python程序中,函数定义必须以“def”开头,因此处理函数的过程相当简单。为了代码简洁,此处假设程序编写规范上在关键字“def”后跟一个空格,然后就是函数名,接着就是参数。没有考虑使用多个空格的情况。
而Python程序中的变量不好处理,因为变量一般不需要事先声明,往往都是直接赋值。因此在程序中首先处理了变量直接赋值的情况。通过匹配单词后接“=”的情况查找变量名。同样,为了代码简洁,仅考虑比较规范整洁的写法,变量名与“=”之间有一空格。另外,还有一类变量是在for循环语句中直接使用的,因此程序中又特别处理了for循环的情况。为了使代码简洁,程序并没有处理变量名重复的情况。
>>> import re
>>> import sys
>>> def DealWithFunc(s):
... r = re.compile(r'''
... (?<=def\s)
... \w+
... \(.*?\)
... (?=:)
... ''',re.x|re.U
... return r.findall(s)
File "<stdin>", line 8
return r.findall(s)
^
SyntaxError: invalid syntax
>>> def DealWithFunc(s):
... r = re.compile(r'''
... (?<=def\s)
... \w+
... \(.*?\)
... (?=:)
... ''',re.x|re.U)
... return r.findall(s)
...
>>> def DealWithVar(s):
... vars = []
... r = re.compile(r'''
... \b
... \w+
... (?=\s=)
... ''',re.x|re.U)
... vars.extend(r.findall(s))
... r = re.compile(r'''
... (?<=for\s)
... \w+
... \s
... (?=in)
... ''',re.X|re.U
... vars.extend()
KeyboardInterrupt
>>> def DealWithFunc(s):
... r = re.compile(r'''
... (?<=def\s)
... \w+
... \(.*?\)
... (?=:)
... ''',re.X|re.U)
... return r.findall(s)
...
>>> def DealWithVar(s):
... vars = []
... r = re.compile(r'''
... \b
... \w+
... (?=\s=)
... ''',re.X|re.U
... )
...
>>> def DealWithVar(s):
... vars = []
... r = re.compile(r'''
... \b
... \w+
... (?=\s=)
... ''',re.X|re.U)
... vars.extend(r.findall(s))
... r = re.compile(r'''
... (?<=for\s)
... \w+
... \s
... (?=in)
... ''',re.X|re.U)
... vars.extend(r.findall(s))
... return vars
...
>>> if len(sys.argv) == 1:
... sour = input("请输入要处理的文件路径")
...
请输入要处理的文件路径
>>> else:
File "<stdin>", line 1
else:
^
SyntaxError: invalid syntax
>>> if len(sys.argv) == 1:
... sour = input("请输入要处理的文件路径")
... else:
... sour = sys.argv[1]
...
请输入要处理的文件路径
>>> file = open(sour,encoding="utf-8")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: ''
>>> if len(sys.argv) == 1:
... sour = sys.argv[1]
... else:
...
KeyboardInterrupt
>>> if len(sys.argv) == 1:
... sour = input("请输入要处理的文件路径")
... else:
... sour = sys.argv[1]
...
13.5 小结
正则表达式的功能非常强大,学习难度也较大,本章以尽量多的操作代码演示了正则表达式的用法。首先介绍了正则表达式的基本元字符、常用正则表达式分析。接着介绍了使用Python的re模块处理正则表达式,如用match函数进行搜索、使用sub函数进行内容替换、使用split函数分割等。接着介绍了将正则表达式编译为对象,以提供更高性能的方法。还介绍了正则表达式中的分组、匹配和搜索的结果对象——Match对象的使用等内容。以后要多编写代码进行学习、验证。在其他程序设计语言中也可以直接使用在这里学习的正则表达式。
13.6 本章习题