前言
字符串是编程中非常常用的数据结构,对它的操作处理也非常重要。所以学透本知识点,对以后开发过程中对字符串处理,特别是爬虫、对用户输入数据的校验等非常重要哦。
对于之前从未接触编程的同学来说,"正则表达式"这个名字或许不那么见名知义,总之一看这个名字就让人感觉不太好理解。其实,正则表达式它还有另外一个名字:规则表达式,个人觉得这个"规则表达式"反而更容易被理解。
正则表达式,也即规则表达式,说白了就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,用这个“规则字符串”来表达对字符串的一种过滤逻辑。
常见的场景:对邮箱格式的校验、对手机号的合法性校验、爬虫中核心数据的获取...
Python 自1.5版本起增加了re
模块,re
模块使 Python 语言拥有全部的正则表达式功能。
compile
函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象。该对象拥有一系列方法用于正则表达式匹配和替换。re
模块也提供了与这些方法功能完全一致的函数,这些函数使用一个模式字符串, 即形参:pattern
,来做为它们的第一个参数。
正则表达式的思想
正则表达式的设计思想是:用一种描述性的语言来定义一个规则,凡是符合此规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的。
我们要学习的就是如何定义这个"规则"。
示例及说明
因为正则表达式也是用字符串表示的,所以我们要首先了解如何用字符来描述字符。
- 精准匹配
在正则表达式中,如果直接给出字符,就是精确匹配。用\d
可以匹配一个数字,\w
可以匹配一个字母或数字,例如:
"123\d"
表示必须是123后跟一位任意数字的,可以匹配:1234
、1235
,但无法匹配123a
"123\w"
表示必须是123后跟一位任意字母或数字的,可以匹配:1234
、1235
、123a
"\d\d\d"
表示3个数字,可以匹配:123
、110
,但无法匹配abc
、12a
·
可以匹配任意字符,例如:
"abc."
表示必须是abc后跟一位任意字符的,可以匹配:abc!
、abc1
、abc,
, 但无法匹配abc
- 不定长匹配
要匹配变长的字符,在正则表达式中,用*
表示任意个字符(包括0个)、用+
表示至少一个字符,用?
表示0个或1个字符,用{n}
表示n个字符,用{n,m}
表示n-m
个字符,例如:
上面例子中的"\d\d\d"
可以简写为"\d{3}"
表示3个数字,可以匹配:123
、110
,但无法匹配abc
、12a
;
"bo*k"可以匹配:bk
、bok
、book
、booooooook
等,但无法匹配boak
此处我们先介绍一个函数:re.findall(matten, string, flags)
功能:在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。
语法格式:re.findall(matten, string, flags)
参数说明:
matten
:正则中的模式字符串。
string
:要匹配的字符串。
flags
:标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。
注意: 后面还会学习另外两个函数match 和 search
,现在你只需知道 match
和 search
是匹配一次,而 findall
匹配所有。
上面规则用代码验证如下:
import re
# 设计的正则表达式
pattern = "bo*k"
# 待匹配的字符串
content = "bk-bok-book-boooooook-boak"
# 用re.findall,在字符串中找到正则表达式所匹配的所有子串,放入一个列表中并返回,如果没有找到匹配的,则返回空列表。
result = re.findall(pattern, content)
print(result)
运行结果:
['bk', 'bok', 'book', 'boooooook']
同理,可以验证:
"wo+w"可以匹配:wow
、woow
、woooooooow
等,但无法匹配ww
、woaw
等;
"wo?w"可以匹配:ww
、wow
等,但无法匹配woow
、woooooooow
、woaw
等;
"wo{2}w"可以匹配:woow
,但无法匹配ww
、wow
、woooooooow
、woaw
等;
"wo{2,10}w"可以匹配:woow
、woooooooow
、,但无法匹配ww
、wow
、woaw
等;
- 字符集 [ ]
对应的位置可以是字符集中任意字符。字符集中的字符可以逐个列出,也可以给出范围,如[abc]
或[a-c]
。
^
匹配字符串开头,^\d
表示必须以数字开头。
$
匹配字符串行末尾,\d$
表示必须以数字结束。
A|B
可以匹配A
或B
,所以(H|h)ello
可以匹配Hello
或者hello
。
你可能注意到了,he
也可以匹配hello
,但是加上^he$
就变成了整行匹配,就只能匹配he
了。
语法元素
符号 | 含义描述 |
---|---|
· | 匹配任意字符,除了换行符,当re.DOTALL 标记被指定时,则可以匹配包括换行符的任意字符。 |
* | 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式 |
+ | 匹配前一个字符1次或多次 |
? | 匹配一个字符0次或1次 |
^ | 匹配字符串开头。在多行模式中匹配每一行的开头 |
$ | 匹配字符串末尾,在多行模式中匹配每一行的末尾 |
| | 或。匹配|左右表达式任意一个,从左到右匹配,如果|没有包括在()中,则它的范围是整个正则表达式 |
[ ] | 字符集。对应的位置可以是字符集中任意字符。字符集中的字符可以逐个列出,也可以给出范围,如[abc]或[a-c]。[^abc]表示取反,即非abc。所有特殊字符在字符集中都失去其原有的特殊含义。用\反斜杠转义恢复特殊字符的特殊含义。 |
{ } | {m}匹配前一个字符m次,{m,n}匹配前一个字符m至n次,若省略n,则匹配m至无限次 |
( ) | 被括起来的表达式将作为分组,从表达式左边开始没遇到一个分组的左括号“(”,编号+1.分组表达式作为一个整体,可以后接数量词。表达式中的|仅在该组中有效。 |
表示特定含义特殊字符:
符号 | 含义描述 |
---|---|
\w | 匹配数字字母下划线 |
\W | 匹配非数字字母下划线 |
\s | 匹配任意空白字符,等价于 [\t\n\r\f] |
\S | 匹配任意非空字符 |
\d | 匹配任意数字,等价于 [0-9]。 |
\D | 匹配任意非数字 |
\A | 匹配字符串开始 |
\z | 匹配字符串结束 |
\Z | 匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。 |
\G | 匹配最后匹配完成的位置。 |
\b | 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。 |
\B | 匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。 |
\n,\t | 匹配一个换行符。匹配一个制表符等 |
re模块
Python 自1.5
版本起增加了re
模块,re
模块使 Python 语言拥有全部的正则表达式功能。
在设计规则表达式时,要特别注意字符转义。由于Python的字符串本身也用\
转义,所以要特别注意:
# Python的字符串
content = 'ABC\\-001'
# 对应的正则表达式字符串变成:
# 'ABC\-001'
因此我们强烈建议使用Python的r
前缀,就不用考虑转义的问题了:
# Python的字符串
content = r'ABC\-001'
# 对应的正则表达式字符串不变:
# 'ABC\-001'
- re.match(pattern, string, flags)判断匹配
re.match
方法尝试从字符串的起始位置去匹配一个模式,判断起始位置是否能匹配,如果匹配成功,返回一个Match
对象,否则返回None
。
函数语法:re.match(pattern, string, flags)
函数参数说明:
参数 | 参数说明 |
---|---|
pattern | 匹配的正则表达式 |
string | 要匹配的字符串 |
flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。 |
【示例】
re.match
方法是从起始位置去匹配的。注意区分下面两种匹配结果:
# 设计的正则表达式,3个数字
patten = r"\d{3}"
# 两个待匹配的测试内容
content = "010-123456789"
content2 = "测试不在起始位置:010-123456789"
# 调用 re.match,获取匹配结果
result = re.match(patten, content)
result2 = re.match(patten, content2)
# 打印匹配结果
print(r"result = {}".format(result))
print(r"result2 = {}".format(result2))
运行结果:
result = <re.Match object; span=(0, 3), match='010'>
result2 = None
- re.split()切分字符串
split
方法按照能够匹配的子串将字符串分割后返回列表。
函数语法:
re.split(pattern, string[, maxsplit=0, flags=0])
函数参数说明:
参数 | 参数说明 |
---|---|
pattern | 匹配的正则表达式 |
string | 要匹配的字符串 |
maxsplit | 分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数。 |
flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。 |
用正则表达式切分字符串比用固定的字符更灵活,常规切分代码:
content = "a b c d e"
print(content.split(" "))
打印切分结果:
['a', 'b', '', '', 'c', 'd', '', '', 'e']
缺陷是无法用连续的空格去切分,而正则表达式可以灵活切分:
content = "a b c d e"
# 用一个空格或连续空格
pattern = r"\s+"
print(re.split(pattern, content))
打印切分结果:
['a', 'b', 'c', 'd', 'e']
如果待分割的字符串中含有了其他字符"a, b; c d e"
,要得到['a', 'b', 'c', 'd', 'e']
,也是可以的:
content = "a, b; c d e"
# 用一个空格或连续空格
pattern = r"[\s,;]+"
print(re.split(pattern, content))
打印切分结果:
['a', 'b', 'c', 'd', 'e']
- 替换re.sub()
Python 的re
模块提供了re.sub
用于替换字符串中的匹配项。
语法:re.sub(pattern, repl, string, count=0)
参数说明:
参数 | 参数说明 |
---|---|
pattern | 匹配的正则表达式 |
repl | 替换的字符串,也可为一个函数。 |
string | 要被查找替换的原始字符串。 |
count | 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。 |
示例如下:
phone = "400-666-1234 # 这是一个电话号码"
# 删除注释
num = re.sub(r'#.*$', "", phone)
print("电话号码 : ", num)
# 移除非数字的内容
num = re.sub(r'\D', "", phone)
print("电话号码 : ", num)
运行结果:
电话号码 : 400-666-1234
电话号码 : 4006661234
- re.finditer
和 findall 类似,在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回。
re.finditer(pattern, string, flags=0)
此处参数和其他意义一样,不再赘述。
- 正则表达式分组功能
除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()
表示的就是要提取的分组(Group)。其中,被括起来的正则表达式将作为一个分组。
^(\d{3,4})-(\d{3,8})$
分别定义了两个组:(\d{3,4})
、(\d{3,8})
,可以直接从匹配的字符串中提取出区号和本地号码:
# 设计正则表达式
pattern = r"(\d{3,4})-(\d{6,8})"
# 待匹配的内容
content = "0755-123456"
# 获取匹配结果
result = re.match(pattern, content)
print(result)
print(result.group())
print("第0组:" + result.group(0))
print("第1组:" + result.group(1))
print("第2组:" + result.group(2))
运行结果:
<re.Match object; span=(0, 11), match='0755-123456'>
0755-123456
第0组:0755-123456
第1组:0755
第2组:123456
一旦正则表达式中定义了组,就可以在Match对象上用group()方法提取出子串来。注意到group(0)
永远是原始字符串,group(1)
、group(2)
……表示第1、2、……个子串。
在上面,当匹配成功时返回一个 Match
对象,其中Match
对象包含的信息:
group([group1, …])
方法用于获得一个或多个分组匹配的字符串,当要获得整个匹配的子串时,可直接使用group()
或 group(0)
;
start([group])
方法用于获取分组匹配的子串在整个字符串中的起始位置(子串第一个字符的索引),参数默认值为 0;
end([group])
方法用于获取分组匹配的子串在整个字符串中的结束位置(子串最后一个字符的索引+1),参数默认值为 0;
span([group])
方法返回 (start(group), end(group))。
贪婪匹配
正则匹配默认是贪婪匹配,也就是每次都是默认去匹配尽可能多的字符。re.compile() 函数编译正则表达式
当我们在Python中使用正则表达式时,re模块内部会干两件事情:
1.编译正则表达式,如果正则表达式合法,编译后生成Pattern
对象。如果正则表达式的字符串本身不合法,会报错;
2.用编译后的正则表达式去匹配字符串。
如果一个正则表达式要重复使用成百上千次,则出于效率的考虑,我们可以预编译该正则表达式,接下来重复使用时就不需要编译这个步骤了,直接匹配。
compile
函数用于编译正则表达式,生成一个正则表达式Pattern
对象,该对象有提供match()
和 search()
这两个函数使用。
语法格式为:re.compile(pattern[, flags])
参数:
参数 | 参数说明 |
---|---|
pattern | 匹配的正则表达式 |
flags | 标志位,可选,表示匹配模式,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。 |
flags 参数可取的值
flags 参数可取的值 | 取值说明 |
---|---|
re.I | 忽略大小写 |
re.L | 表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境 |
re.M | 多行模式 |
re.S | 即为' . '并且包括换行符在内的任意字符(' . '不包括换行符) |
re.U | 表示特殊字符集 \w, \W, \b, \B, \d, \D, \s, \S 依赖于 Unicode 字符属性数据库 |
re.X | 为了增加可读性,忽略空格和' # '后面的注释 |
# 设计正则表达式
pattern = r"(\d{3,4})-(\d{6,8})"
# 编译正则表达式
rex_phone = re.compile(pattern)
# 待匹配的内容
content = "0755-123123"
# 获取匹配结果
result = rex_phone.match(content)
print(result)
print(result.group())
print("第0组:" + result.group(0))
print("第1组:" + result.group(1))
print("第2组:" + result.group(2))
- re.search方法
re.search
扫描整个字符串并返回第一个成功的匹配。
函数语法:re.search(pattern, string, flags=0)
注意
:匹配成功re.search
方法返回一个匹配的对象,否则返回None。
re.match与re.search的区别
re.match
只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None
;而re.search
匹配整个字符串,从开始去找,直到找到一个匹配。如果直到结束还没找到匹配的,才返回None
。
小结
正则表达式,也即规则表达式,说白了就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,用这个“规则字符串”来表达对字符串的一种过滤逻辑。
正则表达式的内容非常丰富,我们只是学习了其中最基础、最为常用的知识点,而这些满足日常开发也绰绰有余了。
更多了解,可关注公众号:人人懂编程