阅读量

原创教程,严禁转载。引用本文,请署名 Python中文网, http://www.zglg.work


十一、Python正则

正则应用广泛,不仅在Python语言中使用,其他语言也都在用,并且不同语言间的正则语法极为相似。同时主流操作系统,尤其linux系统的命令窗口中,也会经常使用到正则。还有,Python的常用包如Pandas,也经常遇到正则。

不仅引用广泛,正则的功能本身很强大,书写简便,因此这项技能值得我们仔细研究和掌握。

1 正则学习前的几个准备

Q1 字符 r是干啥的?

经常见过正则表达式前有一个字符 r,它的作用是告诉解释器后面的一串是原生字符串,按照字面意思解释即可。如:

s1 = r'\n.*'
print(s1) 

它告诉编译器s串第一个字符是\,第二个字符是n.打印的结果就是它本身:

\n.*

而如果不带前缀字符r,即:

s2 = '\n.*'
print(s2)

解释器认知前两个字符\n为转义字符,一个新行的意思,打印结果为一个换行加.*:

.*

Q2 什么是一个原子操作?

微观世界中,如果定义原子是组成事物的最基本单元,那么就可理解为原子不能再分了。同理此处,正则的原子操作是指不能再被分割的正则表达式操作。

如正则中的+指前面的一个原子操作出现至少1次。例如: 66+表示至少要有2个6紧邻(下面会详细讲到);

\w+表示字母数字下划线中的任意一个字符(\w指代的)至少出现1次,那么\w就是一个原子操作。

因此,普通字符是原子,正则中的通用字符(下面会讲到)也是原子。大家记住原子这个概念。

Q3 怎么理解正则中的转义?

正则世界中,重新定义一套新的转义字符。

一个转义字符\+一个字符,转义后会改变原字符的意义,它不再是它,而是赋予一个新的含义。

例如,w本身就是一个英文字符w,没有其他任何指代。但是,前面加一个转义字符 \ 后,含义发生重大改变,w它不再是w,而是\要与w连在一起,被解释器解释为匹配以下字符集合中的任意一个:

pat = '\w'

等于:

pat = '[0123456789
      AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz
      _]'

即匹配数字、大小写字母和下划线_字符中的任意一个。

你看一个通用转义字符\w直接就指代了上面这一大串,写法极其简便,同时在正则的世界里又经常被用到,故被称为:通用正则字符

类似的通用正则字符还有几个,下面也会讲到几个,不过做一件事前,把规则弄清,触类旁通,相信大家理解其他几个也没问题了。

2 掌握最常用规则

为了更清晰的展示规则,我们只涉及最常用的规则,一来缩短篇幅,二来降低大家学习曲线,三来一类问题掌握一个,触类旁通即可。

情况1:最普通查找

最普通查找就是需要找啥就写啥,没有使用规则。如下从小说《灿烂千阳》的这段话中,找出单词friendship,可能出现多次:

s = """
# Mariam is only fifteen 
# when she is sent to Kabul to marry the troubled and bitter Rasheed,
# who is thirty years her senior. 
# Nearly two decades later, 
# in a climate of growing unrest, tragedy strikes fifteen-year-old Laila, 
# who must leave her home and join Mariam's unhappy household. 
# Laila and Mariam are to find consolation in each other, 
# their friendship to grow as deep as the bond between sisters, 
# as strong as the ties between mother and daughter. 
# With the passing of time comes Taliban rule over Afghanistan, 
# the streets of Kabul loud with the sound of gunfire and bombs, 
# life a desperate struggle against starvation, brutality and fear, 
# the women's endurance tested beyond their worst imaginings. 
# Yet love can move a person to act in unexpected ways, 
# lead them to overcome the most daunting obstacles with a startling heroism. 
# In the end it is love that triumphs over death and destruction. 
# A Thousand Splendid Suns is an unforgettable portrait of a wounded country and
#  a deeply moving story of family and friendship. 
#  It is a beautiful, heart-wrenching story of an unforgiving time, 
#  an unlikely bond and an indestructible love.
"""

使用正则前,先导入re模块,再定义正则表达式,然后使用findall方法找出所有匹配

import re
pat = 'friendship'
result = re.findall(pat,s)
print(result) 

# 共找到两处:
# ['friendship', 'friendship']

以上就是使用正则的最普通例子。如果要找出前缀为grow的单词,比如可能为grows, growing 等,最普通查找实现起来就不方便。借助于下面介绍的元字符、通用字符和捕获组合起来能实现强大的查找功能。

情况2:使用通用字符

在正则的世界里,通用字符指帮助我们更加简便的写出匹配规则的字符。如上面这段文字,使用下面正则匹配串找出子串中以d开始,[a-z]表示任意一个小写英文字符,{7}表示小写英文字符出现7次(下面情况3会说到),也就是匹配出来的子串长度为1+7=8:

pat = 'd[a-z]{7}'
result = re.findall(pat,s)

匹配结果为:

['daughter', 'desperat', 'daunting', 'destruct', 'destruct']

同理模式串pat = 'd[a-z]{10}'匹配的结果为:

['destruction', 'destructibl']

模式串pat = 'd[a-z]{11}'匹配的结果为:

[ 'destructible']

你看通用字符[a-z]使用真方便,5个字符一下就表达了所有26个小写的字符,但是注意[a-z]匹配26个小写字符的任意一个.

类似功能的通用字符还包括:

[A-Z]  匹配大写英文字母
[0-9]  匹配一个0-9之间的数字

还有更加强大的通用字符:

\s  匹配空白字符,如\n \t \b等
\w  匹配任意字母、数字、下划线 
\d  匹配十进制数字0-9

而\S, \W, \D 分别对应 \s, \w, \d匹配字符集的补集,例如\S 的意思是匹配 \s 以外的其他任意字符。

情况3:使用元字符

的含义大家不妨理解用来描述它后面事物的类,如元类用来创建描述类的类,元模型描述一个模型的模型,因此推而广之,元字符用来描述字符的字符。

理解以上后,你再看正则中使用最普遍的一个元字符 +,它是用来描述前面一个原子出现次数的字符,表示前一个原子出现1次或多次都可。

例如,在寻找手机靓号时,正则表达式66+,表示前一个原子6至少出现1次,因此连上第一个6,表示电话号码中至少有两个66紧邻。因此,电话号码1861265216617566665656都满足要求,而号码18616161616不符合要求。

类似功能的元字符,还包括如下,功能相似,不再赘述:

* 前面的原子重复0次、1次、多次 
? 前面的原子重复0次或者1次 
+ 前面的原子重复1次或多次
{n} 前面的原子出现了 n 次
{n,} 前面的原子至少出现 n 次
{n,m} 前面的原子出现次数介于 n-m 之间

3 有个棘手的场景

了解以上规则后,我们能去完成很多匹配任务,比如一些字符串匹配任务;查找某个手机号是不是靓号;找到文字中出现某个模式的所有地方。

但是,使用正则还会经常遇到的一大场景。试想,从一个文件里提取出所有如下格式的链接,并全部导出来:

https://github.com/jackzhenguo/python-small-examples

https://gitbook.cn/gitchat/column/5e37978dec8d9033cf916b5d

截止目前,我们还不能完成这样的匹配任务。鉴于这种匹配任务确实很常见,因此你很有必要学会下面这项技能。

4 学会提取子串的技能

今天以我写过的《Python 60天》专栏中的一段文字,提取出里面的链接为例,阐述提取子串的实用性。

先贴上文字(有删减改动),将这段文字赋值给变量 urls

urls = """
基于 Python 的包更是枝繁叶茂,遍地开花,

“Tiobe 编程语言排行榜”最新统计显示 Python 是增长最快的语言。

![image-20200131192231967](https://images.gitbook.cn/2020-02-05-014719.png)

接下来,与大家,还有远在美国做 AI 博士后研究的 Alicia,

一起开始我们的 60 天 Python 探索之旅吧。

所有的这些考虑,都是为了让大家在短时间内掌握 Python 技术栈,

多一个生存的本领。拿到理想的 Offer 后,早日过上自己想要的生活。

让我们开始吧。

如下,按照是否为静态/动态语言,弱类型/强类型两个维度,

总结常用的语言分类。

![image-20200205155429583]

(https://images.gitbook.cn/2020-02-05-080211.png) 

"""

你可能很快写出如下的正则表达式:

# 元字符.表示匹配除\n字符外的任意一个字符
# 元字符*表示匹配前面一个原子0次或多次
pat = r'https:.*' 

然后导入re模块,使用findall方法找出所有匹配:

import re
result = re.findall(pat,urls)
print(result)

运行结果显示如下,观察发现2个匹配,但是每个匹配链接都包括冗余字符,因此匹配错误:

['https://images.gitbook.cn
/2020-02-05-014719.png)',

'https://images.gitbook.cn
/2020-02-05-080211.png) ### 四大基本语法']

我们再稍微优化原正则表达式为:

# 添加 \) 表示待匹配子串以右括号结尾
pat = r'https:.*\)'

打印结果显示如下,结果确实好一点,但是依然包括右括号,结果还是错误的:

['https://images.gitbook.cn/
2020-02-05-014719.png)', 

'https://images.gitbook.cn/
2020-02-05-080211.png)']

所以掌握提取子串的技能就很重要,实现提取子串也很简单,只需把想要返回的子串加上一对括号就行,如下所示:

# 把想要返回的子串外面添加一对括号

pat = r'(https:.*)\)'

此时返回结果完全正确,无任何多余字符。想要返回的子串外面添加一对括号还有个专业叫法:捕获分组

5 使用捕获的注意事项

捕获功能非常实用,使用它需要区分一点,贪婪捕获和非贪婪捕获。前者指在满足匹配模式前提下,返回包括尽可能多的字符匹配模式;后者相对的满足匹配条件下,尽可能少的捕获。

我们伪造一个理想状况下的案例:

htmlContent = """
        <div><div><h2>这是二级标题</h2></div><div><p> 这是一个段落>/p></div></div>
"""

贪心捕获使用(.*),如下所示:

pat = r"<div>(.*)</div>"

result = re.findall(pat,htmlContent)

结果为如下,尽可能长的捕获,而不是遇到第一个</div>时就终止:

['<div><h2>这是二级标题</h2></div><div><p> 这是一个段落>/p></div>']

而非贪心捕获的正则表达式为<div>(.*?)</div>",如下:

pat = r"<div>(.*?)</div>"

result = re.findall(pat,htmlContent)

print(result)

结果为两个元素,遇到第一个</div>时终止,然后继续捕获出第二子串:

['<div><h2>这是二级标题</h2>', 
  '<p> 这是一个段落>/p>']

以上例子仅仅用作演示两者区别,实际的html结构含有换行符等,环境比上面要复杂的多,贪心和非贪心的捕获的写法可能不会导致结果不同,但是我们依然需要理解它们的区别。

Python内置一个时间模块datetime,提供我们关于时间的表达。记录时间无所不在,日志文件,程序运行起始时间和时长,销量预测的特征等等,我们都能看到时间的身影。

这篇专题总结datetime模块最主要用法,希望通过此文,大家使用那些时间处理的常用API时,能信手拈来,不用help函数,不用搜索。


Python 20个专题完整目录:

Python前言

Google Python代码风格指南

Python数字

Python正则之提取正整数和大于0浮点数

Python字符串

CSV读写乱码问题

Unicode标准化

Unicode, UTF-8, ASCII

Python动态生成变量

Python字符串对齐

Python小项目1:文本句子关键词的KWIC显示

Python列表

Python流程控制

Python编程习惯专题

Python函数专题

Python面向对象编程-上篇

Python面向对象编程-下篇

Python十大数据结构使用专题

Python包和模块使用注意事项专题

Python正则使用专题

Python时间专题

Python装饰器专题

Python迭代器使用专题

Python生成器使用专题

Python 绘图入门专题

Matplotlib绘图基础专题

Matplotlib绘图进阶专题

Matplotlib绘图案例

NumPy图解入门