一. 正则表达式基础
正则表达式引擎对正则表达式文本进行编译生成正则表达式对象,再由正则表达式对象对目标文本进行匹配,后返回匹配结果。
正则表达式中一共包含 2 中字符:普通字符
、元字符
- 对于普通字符,匹配字符本身;
- 元字符在正则表达式中具有特殊的含义,按照功能划分为:
匹配单个字符
、匹配重复性
、匹配位置
、分组
、转义元字符
、或
。
匹配单个字符的元字符及其含义:
元字符 | 释意 |
---|---|
. |
匹配单个字符,re.DOTALL 模式中可以匹配 \n 。 |
\d |
匹配数字,相当于 [0-9] 。 |
\D |
匹配非数字,相当于 [^\d] 。 |
\s |
匹配任意空字符,相当于 [\t\n\r\f\v] 。 |
\S |
匹配任意非空字符。 |
\w |
匹配任意一个普通字符:[_0-9a-zA-Z] 。 |
\W |
匹配任意的非普通字符:[^_0-9a-zA-Z] ,即匹配特殊字符。 |
[] |
匹配括号内的任意一个字符。 |
匹配重复性的元字符及其含义:
元字符 | 释意 |
---|---|
* |
匹配前一个字符 0 次或多次,比如:正则表达式 ab* 匹配的是 a ab abb abbbbbbbbbb .... |
+ |
匹配前一个字符 1 次或多次。 |
? |
匹配一个字符 0 次或 1 次,非贪婪匹配。 |
{n} |
匹配前一个字符 n 次。 |
{m,n} |
匹配前一个字符 m~n 次。 |
匹配位置的元字符及其含义:
元字符 | 释意 |
---|---|
^ \A |
匹配字符串开头,在多行模式中匹配每一行的开头。 |
[^] |
匹配字符集以外的任意一个字符,如 [^a-z] 匹配除了 a-z 以外的任意一个字符。 |
$ \Z |
匹配字符串末尾,在多行模式中匹配每一行的末尾。 |
\b |
匹配单词边界。例如:'er\b' 可以匹配 "never" 中的 'er' ,但不能匹配 "verb" 中的 'er' 。 |
\B |
匹配非单词边界。 |
分组匹配的元字符及其含义:
元字符 | 释意 |
---|---|
(?P |
为分组指定一个别名 name 。 |
\number |
引用编号为 number 的分组匹配到的内容,如:r'(\d)abc\1' 可以匹配类似 1abc1 5abc5 ... 的字符串。 |
|
引用别名为 name 的分组匹配到的内容,如:r'(?P 也可以匹配类似 1abc1 5abc5 ...的字符串。 |
表示或运算的元字符:
元字符 | 释意 |
---|---|
| |
或。匹配 | 符号左右表达式任意一个,如果 | 没有包括在 () 中,则它的范围是整个正则表达式。 |
转义元字符:
元字符 | 释意 |
---|---|
\ |
正则表达式在匹配特殊符号时需要转义,如匹配 . 则需要使用 \. 。 |
二. re 模块
在 Python 中使用正则表达式,我们需要借助 re 模块提供的强大 API,下面我们就来学习几个 re 模块常用的接口吧~
findall
参数说明:findall("正则表达式", "要匹配的字符串", flags=标志1|标志2|...)
返回值:以列表形式返回匹配到的字符串。
其中参数
flags
常用的标志如下:
re.S / re.DOTALL
:使.
匹配包括换行在内的所有字符;re.I / re.IGNORECASE
:使匹配对大小写不敏感;re.M / re.MULTILINE
:多行匹配影响^
和$
;re.U / re.UNICODE
:根据 Unicode 字符集解析字符,这个标志影响\w,\W,\b,\B
下面,我们用 findall
返回 Python 之禅中首尾用到的反义词:
import re
zen_of_python = """
The Zen of Python, by Tim Peters
1 Beautiful is better than ugly.
2 Explicit is better than implicit.
3 Simple is better than complex.
4 Complex is better than complicated.
5 Flat is better than nested.
6 Sparse is better than dense.
"""
regex = "\d\s*(\w+)\s*is better than\s*(\w+)\."
re.findall(regex, zen_of_python)
运行结果:
[('Beautiful', 'ugly'),
('Explicit', 'implicit'),
('Simple', 'complex'),
('Complex', 'complicated'),
('Flat', 'nested'),
('Sparse', 'dense')]
当正则表达式中含有一个以上分组时,findall
返回的列表由元组构成,元组中包含每个分组匹配到的内容。如果只有一个分组,则返回由该分组匹配到的内容组所构成的列表:
>> regex = "\d\s*(\w+)\s*is better than"
>> re.findall(regex, zen_of_python)
['Beautiful', 'Explicit', 'Simple', 'Complex', 'Flat', 'Sparse']
match
import re
my_str = "Last Checkpoint: 7小时前 11:23:00 *"
my_regex = r"Last\s+\w+:\s+(\d+)\D+\s+(.*)\s+"
result = re.match(my_regex, my_str)
match
函数返回的结果是一个 SRE_Match
对象:
>> result
<_sre.SRE_Match object; span=(0, 31), match='Last Checkpoint: 7小时前 11:23:00 '>
SRE_Match
具有很多的属性,比如 .string
属性可以方便我们我获取在匹配时输入的字符串:
>> result.string
'Last Checkpoint: 7小时前 11:23:00 *'
属性 .re
可以获取匹配时使用的编译后的正则表达式模式:
>> result.re
re.compile(r'Last\s+\w+:\s+(\d+)\D+\s+(.*)\s+', re.UNICODE)
由于默认使用了 re.UNICODE
,所以我们这里的 \w
可以匹配中文字符。
属性 .regs
则以列表的形式返回正则表达式匹配到的内容以及各个分组陪陪到的内容,不过请注意,返回的都是索引的形式:
>> result.regs
((0, 31), (17, 18), (22, 30))
可以使用序列切片来看一下我们的正则表达式及其中的两个分组所匹配到的内容:
>> my_str[slice(*result.regs[0])]
'Last Checkpoint: 7小时前 11:23:00 '
>> my_str[slice(*result.regs[1])]
'7'
>> my_str[slice(*result.regs[2])]
'11:23:00'
SRE_Match
对象也提供了非常多好用的方法,比如 groups
可以获取各个分组匹配到的内容:
>> result.groups()
('7', '11:23:00')
group
则可以灵活地获取正则表达式或对应分组匹配到的内容:
>> result.group(0)
'Last Checkpoint: 7小时前 11:23:00 '
>> result.group(1)
'7'
>> result.group(2)
'11:23:00'
如果使用的正则表达式定义了分组的名称,group
还可以通过名称获取相应分组匹配的内容:
>> my_regex = r"Last\s+\w+:\s+(?P\d+)\D+\s+(?P.*)\s+"
>> result = re.match(my_regex, my_str)
>> result.group('hours')
'7'
>> result.group('times')
'11:23:00'
在定义了分组的名称之后,还可以方便地使用 groupdict
以字典的形式返回所有分组匹配的结果:
>> result.groupdict()
{'hours': '7', 'times': '11:23:00'}
最后需要注意的是,match
从字符串的开头开始匹配,如果开头不符合要求,则直接返回 None
。
>> result = re.match(my_regex, "PYTHON"+my_str)
>> print(result)
None
search
与 match
匹配开头不同,search
匹配第一个符合规则的字符串,未成功则返回 None
。参数:re.search(pattern, string, flags=0)
;返回值同 match
。
在介绍 match
的使用时,最后一个例子,由于我们在字符串前面添加了 'PYTHON' 导致 match
使用原来的正则表达式无法匹配,返回 None
。此时,使用 search
就可以迎刃而解啦:
import re
my_str = "PYTHON Last Checkpoint: 7小时前 11:23:00 *"
my_regex = r"Last\s+\w+:\s+(?P\d+)\D+\s+(?P.*)\s+"
result = re.search(my_regex, my_str)
运行结果:
>> result
>> result.groupdict()
{'hours': '7', 'times': '11:23:00'}
>> result.group(0)
'Last Checkpoint: 7小时前 11:23:00 '
>> result.group('hours')
'7'
>> result.group('times')
'11:23:00'
小结:
split
参数:re.split(pattern, string, maxsplit=0, flags=0)
,split
功能非常强大,以正则表达式匹配到的标志来分隔字符串,比如下面这样一个混乱的字符串,我们要提取其中所有的数字:
>> my_str = "1*2+3-4^5"
>> my_regex = "[\*\+\-\^]"
>> re.split(my_regex, my_str)
['1', '2', '3', '4', '5']
下面,我们就来详细介绍一下 split
的用法。
首先,是以单字符切割:
>> line = 'a b c;d;;e,f,,g'
>> re.split(';',line)
['a b c', 'd', '', 'e,f,,g']
以分号切割时,共产生了 4 个子字符串,放在列表中返回。
下面,还是以单字符切割,但可以使用正则表达式中的 []
来指定多种字符:
>> re.split('[;\s,]', line)
['a', 'b', '', 'c', 'd', '', 'e', 'f', '', 'g']
由于字符串 line
中有连续的 2 个分号,逗号或者空格,因此可以使用 [;\s,]+
来切割:
>> re.split('[;\s,]+', line)
['a', 'b', 'c', 'd', 'e', 'f', 'g']
最后,上面的字符串在切割时,分隔符都没有被保留下来,使用括号捕获分组,即可保留分隔符:
>> re.split('([;\s,]+)', line)
['a', ' ', 'b', ' ', 'c', ';', 'd', ';;', 'e', ',', 'f', ',,', 'g']
sub / subn
-
re.sub(pattern, repl, string, count=0, flags=0)
将匹配到的内容替换为字符串repl
,返回替换后的字符串。 -
re.subn(pattern, repl, string, count=0, flags=0)
将匹配到的内容替换为字符串repl
,并返回替换的次数。
re.sub
提供比字符串的 replace
方法更加强大的功能:对于输入的字符串 string
,利用正则表达式 pattern
强大的字符串处理功能,实现复杂的字符串替换处理为 repl
,返回被替换后的字符串。
下面的例子中,我们将句子中多余的空格和数字去掉:
>> inputStr = "hello 123 python 456"
>> re.sub('\s\d+', '', inputStr)
'hello python'
如果想要知道替换过程中,共发生了多少次替换,可以使用 subn
:
>> re.subn('\s\d+', '', inputStr)
('hello python', 2)
小结:
compile
上述的案例中,我们每次都需要传入正则表达式,相应的函数每次在调用时,都需要编译一次正则表达式。如果上述过程需要多次重复,那么每次都去耗费时间编译正则表达式是很不划算的。
re 模块为我们提供了 compile
函数,用来编译正则表达式模式,返回编译好模式。因此,可以把那些常用的正则表达式编译成正则表达式对象,以提高效率。
格式:re.compile(pattern, flags=0)
,其中 pattern
为编译时用的表达式字符串,flags
为编译标志位,用于修改正则表达式的匹配方式,如:是否区分大小写,多行匹配等。常用的 flags
有:
注:使用按位或 |
连接多个 flags
。
我们上述介绍的 re 模块的匹配、分割、替换函数,compile
函数的返回值类提供了相应的方法,使用方式类似,只是不需要传入正则表达式字符串而已。
>> pattern1 = re.compile('\s\d+')
>> inputStr = "hello 123 python 456"
>> pattern1.sub('', inputStr)
'hello python'
>> pattern1.subn('', inputStr)
('hello python', 2)
由于用法几乎一致,这里就不一一举例啦~
三. 贪婪匹配和非贪婪匹配
贪婪模式:*
+
?
{m,n}
,正则表达式的重复默认总是尽可能多得向后匹配内容。
>> re.findall(r'ab*',"abbbbbde")
['abbbbb']
>> re.findall(r'ab+',"abbbbbde")
['abbbbb']
>> re.findall(r'ab?',"abbbbbde")
['ab']
>> re.findall(r'ab{3,5}',"abbbbbde")
['abbbbb']
非贪婪模式:*?
+?
??
{m,n}?
,尽可能少的匹配内容。
>> re.findall(r'ab*?',"abbbbbde")
['a']
>> re.findall(r'ab+?',"abbbbbde")
['ab']
>> re.findall(r'ab??',"abbbbbde")
['a']
>> re.findall(r'ab{3,5}?',"abbbbbde")
['abbb']