42.Python编程:Python正则表达式

前言

字符串是编程中非常常用的数据结构,对它的操作处理也非常重要。所以学透本知识点,对以后开发过程中对字符串处理,特别是爬虫、对用户输入数据的校验等非常重要哦。

对于之前从未接触编程的同学来说,"正则表达式"这个名字或许不那么见名知义,总之一看这个名字就让人感觉不太好理解。其实,正则表达式它还有另外一个名字:规则表达式,个人觉得这个"规则表达式"反而更容易被理解。

正则表达式,也即规则表达式,说白了就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,用这个“规则字符串”来表达对字符串的一种过滤逻辑。

常见的场景:对邮箱格式的校验、对手机号的合法性校验、爬虫中核心数据的获取...

Python 自1.5版本起增加了re模块,re 模块使 Python 语言拥有全部的正则表达式功能。

compile 函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象。该对象拥有一系列方法用于正则表达式匹配和替换。re模块也提供了与这些方法功能完全一致的函数,这些函数使用一个模式字符串, 即形参:pattern,来做为它们的第一个参数。

正则表达式的思想

正则表达式的设计思想是:用一种描述性的语言来定义一个规则,凡是符合此规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的。

我们要学习的就是如何定义这个"规则"。

示例及说明

因为正则表达式也是用字符串表示的,所以我们要首先了解如何用字符来描述字符。

  • 精准匹配

在正则表达式中,如果直接给出字符,就是精确匹配。用\d可以匹配一个数字,\w可以匹配一个字母或数字,例如:
"123\d"表示必须是123后跟一位任意数字的,可以匹配:12341235,但无法匹配123a
"123\w"表示必须是123后跟一位任意字母或数字的,可以匹配:12341235123a
"\d\d\d"表示3个数字,可以匹配:123110,但无法匹配abc12a
·可以匹配任意字符,例如:
"abc."表示必须是abc后跟一位任意字符的,可以匹配:abc!abc1abc,, 但无法匹配abc

  • 不定长匹配

要匹配变长的字符,在正则表达式中,用*表示任意个字符(包括0个)、用+表示至少一个字符,用?表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n-m个字符,例如:
上面例子中的"\d\d\d"可以简写为"\d{3}"表示3个数字,可以匹配:123110,但无法匹配abc12a
"bo*k"可以匹配:bkbokbookbooooooook等,但无法匹配boak


此处我们先介绍一个函数:re.findall(matten, string, flags)
功能:在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。

语法格式:re.findall(matten, string, flags)
参数说明:
matten:正则中的模式字符串。
string:要匹配的字符串。
flags:标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。
注意: 后面还会学习另外两个函数match 和 search,现在你只需知道 matchsearch是匹配一次,而 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"可以匹配:wowwoowwoooooooow等,但无法匹配wwwoaw等;
"wo?w"可以匹配:wwwow等,但无法匹配woowwoooooooowwoaw等;
"wo{2}w"可以匹配:woow,但无法匹配wwwowwoooooooowwoaw等;
"wo{2,10}w"可以匹配:woowwoooooooow、,但无法匹配wwwowwoaw等;

  • 字符集 [ ]

对应的位置可以是字符集中任意字符。字符集中的字符可以逐个列出,也可以给出范围,如[abc][a-c]

^匹配字符串开头,^\d表示必须以数字开头。
$匹配字符串行末尾,\d$表示必须以数字结束。
A|B可以匹配AB,所以(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

小结

正则表达式,也即规则表达式,说白了就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,用这个“规则字符串”来表达对字符串的一种过滤逻辑。

正则表达式的内容非常丰富,我们只是学习了其中最基础、最为常用的知识点,而这些满足日常开发也绰绰有余了。


更多了解,可关注公众号:人人懂编程


微信公众号:人人懂编程
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容