01. 正则表达式基本介绍
1.1 正则表达式介绍
- 正则表达式是一种特殊的字符串,是一款常用于校验、筛选、检索、替换字符串内容的工具。
- 正则表达式有其自己独特的语法,以及处理引擎,在所有编码语言中几乎都存在。
- 正则表达式的执行效率比Python低。
- 因此能用Python完成的,就用Python完成,Python完成不了的再用正则表达式。
- 在线练习网站:https://regex101.com/
教程网站:https://github.com/ziishaned/learn-regex/blob/master/translations/README-cn.md
1.2 正则表达式应用场景
注册时用户名、密码、手机号码、邮箱等信息的格式校验。
-
02. 正则表达式常用语法
2.1 基本匹配
正则表达式其实就是在执行搜索时的格式,它由一些字母和数字组成。例如最简单的正则表达式123,就表示一个规则:由数字1开始,接着是2,再接着是3。
- 正则表达式是大小写敏感的,比如abc不会匹配AbC
“the” == The fat cat sat on the mat.
2.2 元字符
正则表达式主要依赖于元字符。 元字符不代表他们本身的字面意思,他们都有特殊的含义。
2.2.1 .点运算符
“.”可以匹配任意单个字符,但不匹配换行符\n。
“.ar” == The car parked in the garage.
2.2.2 []字符集与^非
- 字符集也称字符类。方括号用来指定一个字符集,如
[1bm]
找的是1或者b或者m。 - 像这种ASCII码连续的,可以使用“-”来连接,如:
[0-9]
、[a-z]
、[A-Z]
、[a-zA-Z0-9]
。 在方括号中使用连字符来指定字符集的范围,这种方式下在方括号中的字符集不关心顺序。
“[a-d][Hh]e” == There are some words: ahe bHe cHe dHe.
方括号中的点”.”就表示句号。
“ar.” == A garage is a good place to park a car. “ar[.]” == A garage is a good place to park a car.
在方括号内以
"^"
开头的字符是否定字符,表示这个字符集是否定的,比如[^N]BA就不可能匹配”NBA”“[c]ar” == The car parked in the garage. “[^c]ar” == The car parked in the garage.
2.2.3 +、*、?重复次数
- 后面跟着元字符+、*、?的,用来指定匹配子模式的次数,这些元字符在不同的情况下有着不同意思。
号:匹配在号之前的字符出现大于等于0次(即可以不出现,也可以出现多次)。
例如表达式a匹配0或更多个以a开头的字符。abc就是匹配以a开头,c结尾,中间有n个b的字符。
“[a-z]“ == The car parked in the garage. “abc” == ac adc abc adddddddddc abbbbbbc
字符和.字符搭配可以匹配所有的字符.。
“.“ == *Apache Hadoop 3.1.4.
*字符和表示匹配空格的符号\s连用,可以表示任意个空格,包括0个。
- 如\sword\s可以匹配0个或多个空格开头且以0个或多个空格结尾的word字符串。
“\scat\s“ == The fat cat sat on the concatenation.
+号:匹配+号之前的字符出现 >=1 次,即至少一次。
- 例如表达式c.+t 匹配以首字母c开头以t结尾,中间跟着至少一个字符的字符串。
“c.+t” == ct cat cabct mat “c.+t” == The fat cat sat on the mat.
- 例如表达式c.+t 匹配以首字母c开头以t结尾,中间跟着至少一个字符的字符串。
?号:标记在符号前面的字符为可选,即出现 0 或 1 次(但不可出现多次)。
- 例如,表达式[T]?he匹配字符串he和The。
“[T]he” == The car is parked in the garage. “[T]?he” == The car is parked in the garage.
- 例如,表达式[T]?he匹配字符串he和The。
2.2.4 {}重复出现的次数
- {}是一个量词,用来限定一个或一组字符可以重复出现的次数。
{m} 表示前面的符号连续出现m次,例如:匹配3位数的数字。
“[0-9]{3}” == The number was 9.99977 but we rounded it off to 10.0.
{m,} 表示前面的符号连续出现最少m次,例如:匹配最少两位的数字。
“[0-9]{2,}” == The number was 9.99977 but we rounded it off to 10.0.
{m,n} 表示前面的符号连续出现最少m次,最多n次,例如:匹配2~3位的数字。
“[0-9]{2,3}” == The number was 9.99977 but we rounded it off to 10.0.
2.2.5 (…)特征标群(分组匹配)
- 类似于数学中的小括号,(..)中的内容会被看成一个整体,多个整体之间用
|
分割。“(ab)“ == abbbbc ababc ababababccccab abbbbbbbbbcccddd “(c|g|p)ar” == The car is parked in the garage. “(he.{2}|ni.{2})” == headnicegoodbyehello*heatnight
2.2.6 |或运算符
- 或运算符就表示或,用作判断条件
例如(T|t)he表示The或the,(T|t)he|car表示(T|t)he或car
“(N|C)BA|basketball” == NBA and CBA are basketball association.
注意:或运算是会短路的,因此需要把大范围写在前面,小范围写在后面。
-
2.2.7 \转码特殊字符
反斜线”\”在表达式中用于转码紧跟其后的字符。
- 换言之,如果想要匹配这些特殊字符则要在其前面加上反斜线\。
- 可转码的字符:{、}、[、]、/、\、+、*、.、$、^、|、?
“(f|c|m)at.?” == The fat cat sat on the mat. “{ | }“ == public void main(String[] args) { System.out.println(“Hello World!”); }
2.2.8 ^开头锚点与$结尾锚点
^a:以a开头的字符串
“^(c|p|g)ar” == car parked agegar “^(c|p|g)ar” == car parked agegar
a$:以a结尾的字符串
“(c|p|g)ar$” = car parked agegar
2.3 简写字符集
- . 除换行符\n外的所有字符。
- \w 匹配所有数字字母下划线(包括英文字母以及其他文字的字母)。
- \W 匹配所有非字母非数字非下划线的符号,等同于[^\w]
- \d 匹配数字,等同于[0-9]
- \D 匹配非数字,等同于[^\d]
- \f 匹配一个换页符
- \n 匹配一个换行符
- \r 匹配一个回车符
- \t 匹配一个制表符
- \v 匹配一个垂直制表符
- \p 匹配CR/LF(等同于\r\n),用来匹配DOS行终止符
- \s 匹配所有空格字符,等同于[\t\n\f\r\p{Z} ]
- \S 匹配所有非空格字符,等同于[^\s]
- \b 匹配非连续符号的边界
-
03. Python中的正则表达式
3.1 re模块概述
Python中用re库处理正则表达式,re是Python的自带的库,因此无需下载,直接导入即可。
import re
3.2 匹配操作
3.2.1 compile()正则对象
若要在Python中用正则表达式匹配内容,则第一步就是要生成正则表达式对象。
re.compile(正则表达式[, 匹配模式])
可以根据正则表达式语法生成正则对象。 ```python import re
匹配以h开头,第二个字符是为\n的任意字符,第三个字符为a,没有限制结尾的内容。
pattern_obj = re.compile(r”^h.a”) # 正则中的所有字符串都是原生字符串,用r修饰。 print(pattern_obj) # re.compile(‘^h.a’)
<a name="bO1Nw"></a>
#### 3.2.2 match()校验匹配
- `正则对象.match(原始字符串数据)`:会从头开始验证原始字符串数据是否匹配正则对象。
- 如果正则对象的语法中没有限制内容的结尾,match()就会校验原始字符串数据是否以正则语法可匹配的内容开头。
- 如果正则对象的语法中限制了内容的结尾,match()就会校验原始字符串数据的内容是否满足正则语法的格式。
- 若能校验成功,就返回对应的匹配match对象,若校验失败,则返回None。
- 示例一(没有限制内容的结尾):验证head、hand是否匹配4.2.1中的pattern_obj。
```python
import re
pattern_obj = re.compile(r"^h.a")
res1 = pattern_obj.match("head")
print(res1) # <re.Match object; span=(0, 3), match='hea'>,校验成功,返回对应的匹配match对象。
res2 = pattern_obj.match("hand")
print(res2) # None,校验失败,返回None
# 因为match()本来就是从头开始匹配的,因此正则中不限定开头也是可以的。
pattern_obj = re.compile(r"h.a")
res1 = pattern_obj.match("head")
res2 = pattern_obj.match("hand")
# 打印发现效果都是一样的。
print(res1, res2) # <re.Match object; span=(0, 3), match='hea'> None
- 示例二(限制内容的结尾):验证hand、hands是否以h开头,以d结尾。 ```python import re
pattern_obj = re.compile(r”^h.*d$”)
res1 = pattern_obj.match(“hand”)
print(res1) #
res2 = pattern_obj.match(“hands”) print(res2) # None,校验失败,返回None
<a name="hI93m"></a>
#### 3.2.3 fullmatch()校验匹配
- `正则对象.fullmatch(原始字符串数据)`:等价于`match()`的第二种情况,即从头到尾进行验证。
```python
import re
pattern_obj = re.compile(r"h.*d") # 没有指定开头和结尾
res1 = pattern_obj.fullmatch("hand")
print(res1) # <re.Match object; span=(0, 4), match='hand'>,校验成功,返回对应的匹配match对象。
res2 = pattern_obj.fullmatch("hands")
print(res2) # None,校验失败,返回None
3.2.4 匹配实战(用户名是否合法)
- 需求:输入一个用户名,判断用户名是否合法。要求用户名由6~12位的英文字母或数字组成,且不能由数字开头。
- 正则分析:
- 因为不能由数字开头,因此第一个字符只能是英文字母(包括大小写),故正则开头为:
^[a-zA-Z]
。 - 除了开头,其他字符由英文字母或数字组成,即
[a-zA-Z0-9]
,且除去开头字符,剩余长度应该为5~11位,组合起来即为[a-zA-Z0-9]{5,11}
- 最终的正则表达式为:
^[a-zA-Z][a-zA-Z0-9]{5,11}
。
- 因为不能由数字开头,因此第一个字符只能是英文字母(包括大小写),故正则开头为:
- 代码实现: ```python import re
username = input(“username = “) pattern_obj = re.compile(r”^[a-zA-Z][a-zA-Z0-9]{5,11}”) if pattern_obj.fullmatch(username) is not None: print(f”{username=}合法”) else: print(f”{username=}不合法”)
<a name="rHkJg"></a>
### 3.3 获取匹配对象数据
<a name="Am3h6"></a>
#### 3.3.1 match对象分析
- match()/fullmatch()方法若校验成功,则会返回对应的匹配match对象,如:`<re.Match object; span=(0, 3), match='hea'>`
- match对象的输出格式如下:
- re.Match object:代表这是一个match对象。
- span=(0, 3):代表匹配到的内容从原始字符串数据的0索引开始,到3索引结束(不包括3)。
- match='hea':代表匹配上的内容为hea。
<a name="iPRkc"></a>
#### 3.3.2 span()获取起止索引
- `match对象.span()`:可以获取匹配内容在原始字符串中的起止索引,即match对象中的span,且获取到的内容是元组类型的数据。
```python
import re
pattern_obj = re.compile(r"h.a")
res = pattern_obj.match("head")
# 在获取内容前一定要判断是否为None,否则可能会出现AttributeError异常。
if res is not None:
print(res.span(), type(res.span())) # (0, 3) <class 'tuple'>
3.3.3 group()分组获取匹配文本
- 默认情况下,
match对象.group()
可以获取匹配上的内容,即match对象中的match。 ```python import re
pattern_obj = re.compile(r”h.a”) res = pattern_obj.match(“head”) if res is not None: print(res.group()) # hea
- group()函数详解:
- group这个单词是分组的意思,之所以可以获取匹配的文本内容,是因为group()在默认情况下会将整个正则表达式看成一个大组。
- 因为缺省情况下没有传入任何限制参数,因此group()函数会获取整个正则匹配的内容,也就实现了它的默认功能,即获取正则匹配的文本。
- 除此之外,group()还可以传入一个int类型的小组索引和str类型的小组名来获取指定小组的内容。
- 以int类型分组:在整个正则表达式中,group()会将一个特征标群分为一个小组,然后从左到右进行从1~n的排序。
- 现有一个邮箱正则表达式:`^[a-zA-Z0-9_]{6,18}@(qq|sina|163|126)\.(com|net|cn)$`。
- 邮箱的格式:用户名@邮箱公司名称.后缀。
- 用户名:要求由数字、英文字母、下划线组成,长度为6~18位,故正则为:`[a-zA-Z0-9_]{6,18}`
- 邮箱公司:常见的有qq、sina、163、126,故正则为:`(qq|sina|163|126)`
- 后缀地址:常见的有.com、.net、.cn,故正则为:`\.(com|net|cn)`
- 要求输入一个邮箱地址,若地址合法,则输出这个邮箱地址以及公司、后缀地址。
```python
import re
# 输入邮箱,并判断邮箱是否合法。
email = input("请输入一个邮箱地址:")
pattern_obj = re.compile(r"^[a-zA-Z0-9_]{6,18}@(qq|sina|163|126)\.(com|net|cn)$")
res = pattern_obj.match(email)
# 若邮箱合法,则输出这个邮箱地址以及公司、后缀地址;否则提示邮箱不合法。
if res is not None:
print(f"邮箱地址:{res.group()}")
print(f"邮箱公司:{res.group(1)}") # 从左往右数第一个特征标群,即:(qq|sina|163|126)
print(f"后缀地址:{res.group(2)}") # 从左往右数第二个特征标群,即:(com|net|cn)
else:
print("邮箱不合法!")
"""
运行结果:
请输入一个邮箱地址:8120398@qq.com
邮箱地址:8120398@qq.com
邮箱公司:qq
后缀地址:com
"""
- 以str类型分组:可以先在正则表达式中给特征标群命名,然后根据标群名获取指定小组的数据。
- 如用
(?P<标群名>正则)
的形式给邮箱公司和后缀地址命名:^[a-zA-Z0-9_]{6,18}@(?P<company>qq|sina|163|126)\.(?P<end_address>com|net|cn)$
- 然后用
group(标群名)
的形式即可获取指定的数据。 ```python import re
- 如用
email = input(“请输入一个邮箱地址:”)
patternobj = re.compile(r”^[a-zA-Z0-9]{6,18}@(?P
if res is not None: print(f”邮箱地址:{res.group()}”) print(f”邮箱公司:{res.group(‘company’)}”) print(f”后缀地址:{res.group(‘end_address’)}”) else: print(“邮箱不合法!”)
“”” 运行结果: 请输入一个邮箱地址:8120398@qq.com 邮箱地址:8120398@qq.com 邮箱公司:qq 后缀地址:com “””
<a name="QW9SA"></a>
### 3.4 查找操作
<a name="bOcGw"></a>
#### 3.4.1 search()查找首个
- `正则对象.search(原始字符串数据)`:会在字符串数据中搜索满足正则对象的内容,如果有满足的内容,则返回查找到的第一个满足要求的Match对象,如果没有满足的内容,则返回None。
- 示例:下例程序中a1、b2、c3、d4都是满足正则要求的字符,但search()只获取到了第一个a1的Match对象。
```python
import re
pattern_obj = re.compile("[a-z][0-9]")
print(pattern_obj.search("a1b2c3d4")) # <re.Match object; span=(0, 2), match='a1'>
3.4.2 findall()查找所有
正则对象.findall(原始字符串数据)
:会在字符串数据中搜索满足正则对象的内容,如果有满足的内容,则返回存有查找到的所有满足要求的字符内容的列表,如果没有满足的内容,则返回空列表[]
。- 示例一:4.3.1中的示例程序用findall()得到的就是一个存有匹配结果字符的列表。 ```python import re
pattern_obj = re.compile(“[a-z][0-9]”) print(pattern_obj.findall(“a1b2c3d4”)) # [‘a1’, ‘b2’, ‘c3’, ‘d4’]
- 示例二:查找字符串`s = "headnicegoodbyehelloheatnight"`中所有以"he"开头,长度为4的字符串。
```python
import re
s = "headnicegoodbyehelloheatnight"
pattern_obj = re.compile(r"he.{2}")
res = pattern_obj.findall(s)
print(res) # ['head', 'hell', 'heat']
3.4.3 findall()的弊端
- 引入案例:
- 现有一段文本数据:
data = "明明邮箱1234566@163.com 清清邮箱地址agdha34345@126.net 欢欢邮箱3452658@qq.com"
- 要求提取出所有邮箱地址。
- 现有一段文本数据:
- 代码实现: ```python import re
data = “明明邮箱1234566@163.com 清清邮箱地址agdha34345@126.net 欢欢邮箱3452658@qq.com” patternobj = re.compile(r”[a-zA-Z0-9]{6,18}@(qq|sina|163|126).(com|net|cn)”) res = pattern_obj.findall(data) print(res) # [(‘163’, ‘com’), (‘126’, ‘net’), (‘qq’, ‘com’)]
- 可以很明显的发现,打印的结果与运行结果是不一样的,这是由于findall()自身的弊端所导致的。
- findall()的弊端:当正则表达式中有特征标群进行分组时,结果中只展示特征标群的内容,而不是整个正则表达式的内容。
<a name="i4Lrx"></a>
#### 3.4.4 finditer()查找所有
- finditer()实际上很好的解决了findall()的弊端问题。
- `正则对象.finditer(原始字符串数据)`:会在字符串数据中搜索满足正则对象的内容。若有满足的内容,则把匹配内容的Match对象放到迭代器中。
- 示例:用finditer()实现4.4.3中的程序。
```python
import re
data = "明明邮箱1234566@163.com 清清邮箱地址agdha34345@126.net 欢欢邮箱3452658@qq.com"
pattern_obj = re.compile(r"[a-zA-Z0-9_]{6,18}@(qq|sina|163|126)\.(com|net|cn)")
res_iter = pattern_obj.finditer(data)
print(res_iter) # <callable_iterator object at 0x000002367F0884F0>,结果是一个迭代器
# 遍历迭代器,获取match
for match_obj in res_iter:
print(match_obj, match_obj.group())
"""
运行结果:
<re.Match object; span=(4, 19), match='1234566@163.com'> 1234566@163.com
<re.Match object; span=(26, 44), match='agdha34345@126.net'> agdha34345@126.net
<re.Match object; span=(49, 63), match='3452658@qq.com'> 3452658@qq.com
"""
# 纯邮箱列表版
import re
data = "明明邮箱1234566@163.com 清清邮箱地址agdha34345@126.net 欢欢邮箱3452658@qq.com"
pattern_obj = re.compile(r"[a-zA-Z0-9_]{6,18}@(qq|sina|163|126)\.(com|net|cn)")
email_list = []
for match_obj in pattern_obj.finditer(data):
email_list.append(match_obj.group())
print(email_list) # ['1234566@163.com', 'agdha34345@126.net', '3452658@qq.com']
3.5 贪婪匹配
3.5.1 贪婪匹配概述
- *(≥0)、+(≥1)、{m,}(≥m)这三个正则符号匹配的次数都是没有上限的,因此被称为贪婪匹配。
- 如有一段HTML文本:
<div>asdasdau1289u,,12jasdm.mkk</div><div></div>
,要求匹配出文中的第一个div标签对。 ```python import re
s_str = r”
- 可以发现,`<div>asdasdau1289u,,12jasdm.mkk</div><div></div>`并不是我们想要的结果(`<div>asdasdau1289u,,12jasdm.mkk</div>`)。但是`<div>asdasdau1289u,,12jasdm.mkk</div><div></div>`却也是满足正则`^<div>.*</div>$`的要求的,即以<div>开头,以</div>结尾,中间是任意长度的任意字符。
- 这种情况就是贪婪匹配的一种体现,即虽然匹配到第一个`</div>`时已经满足匹配要求了,但是加上后面的`<div></div>`还是满足要求的,所以正则表达式就会尽可能多的匹配下去,就出现了上述的情况。
<a name="GJkOQ"></a>
#### 3.5.2 惰性匹配
- 所谓惰性匹配就是要让正则表达式在第一次满足要求后就停下来,用4.3.1中的例子就是在匹配到第一个`</div>`后就停下,不再继续匹配后面的`</div>`。
- 可以用`?`来实现惰性匹配,即:`*?`、`+?`、`{m,}?`
- 示例:完善4.3.1中的程序。
```python
import re
s_str = r"<div>asdasdau1289u,,12jasdm.mkk</div><div></div>"
pattern_obj = re.compile(r"<div>.*?</div>") # 用?限制贪婪匹配,且不限制开头和结尾。
d_str = pattern_obj.search(s_str).group()
print(d_str) # <div>asdasdau1289u,,12jasdm.mkk</div>
3.6 特定内容提取
3.6.1 提取数字
- 匹配数字的正则:
[-]?([1-9][0-9]+|[0-9])(\.[0-9]+)?
- 正负号:正数没有负号,负数有符号,因此符号要出现0次或1次,正则:
[-]?
- 整数正则:
[-]?([1-9][0-9]+|[0-9])
- 一位数的情况:一位数就是0~9,正则:
[-]?[0-9]
- 多位数的情况:首位不能为0,只能是1~9;之后的位数可以是0~9,且至少出现一次,正则:
[-]?[1-9][0-9]+
- 一位数的情况:一位数就是0~9,正则:
- 小数正则:
([-]?[1-9][0-9]+|[-]?[0-9])\.[0-9]+
- 小数正则实际上就是在整数正则的基础上加上小数部分。
- 小数部分包括一个小数点:
\.
,以及小数的数字(0~9的数字,出现至少一次):[0-9]+
- 整数与小数整合:
[-]?([1-9][0-9]+|[0-9])(\.[0-9]+)?
- 小数部分正则出现一次即为小数。
- 小数部分正则出现零次即为整数。
- 正负号:正数没有负号,负数有符号,因此符号要出现0次或1次,正则:
- 示例:从数据
data = "-23.8good$^28cnmxs3.14¥%*&123"
中提取所有数字信息。 ```python import re
data = “-23.8good$^28cnmxs3.14¥%*&123” pattern_obj = re.compile(r”[-]?([1-9][0-9]+|[0-9])(.[0-9]+)?”) res_iter = pattern_obj.finditer(data) res_list = [eval(match.group()) for match in res_iter] print(res_list) # [-23.8, 28, 3.14, 123]
- 2.2.6短路描述:若`([1-9][0-9]+|[0-9])`变成了`([0-9]|[1-9][0-9]+)`,则所有多位数数据匹配到一位数就短路了,不会匹配到后面的多位数正则。
```python
import re
data = "-23.8good$^28cnmxs3.14¥%*&123"
# 大范围在前,小范围在后
pattern_obj1 = re.compile(r"([1-9][0-9]+|[0-9])")
res1 = pattern_obj1.findall(data)
print(res1) # ['23', '8', '28', '3', '14', '123']
# 小范围在前,大范围在后
pattern_obj2 = re.compile(r"([0-9]|[1-9][0-9]+)")
res2 = pattern_obj2.findall(data)
print(res2) # ['2', '3', '8', '2', '8', '3', '1', '4', '1', '2', '3']
-
3.6.2 边界匹配
边界匹配使用\b、\B这两个简写字符集完成:
- \b:匹配非连续符号的边界。
- \B:匹配非边界,即左右都有内容的连续字符。
- 示例1:用\b找出文本中所有以w开头的单词。
- 首先,一句英文句子中的单词前后是不连续的,不连续就是有边界,即\bword\b。
- 接着,w开头的单词第一个字符一定是w,而单词是由一个或多个字母组成的内容。
- 即w开头的单词的正则为:
\bw.+?\b
(?是用来限制贪婪匹配的) ```python import re
s = ‘’’my parents pay special attention to my education. since I was three years old, they start to hire a tutor to teach me english. as the saying that interest is the best teacher, indeed, i listen to a lot of english songs and i am so eager to learn more about the world. mastering english is the key to know more about the world.’’’
pattern_obj = re.compile(r”\bw.+?\b”) res = pattern_obj.findall(s) print(res) # [‘was’, ‘world’, ‘world’]
- 示例2:用找出包含en的单词。
- 首先,一句英文句子中的单词前后是不连续的,不连续就是有边界,即\bword\b。
- 接着,所谓包含en,就是单词中有en这个字符串,有以下三种情况:
- en开头,即en前面没有任何字母,en后面有任意个字母。
- en在中间,即en前后都有任意个字母。
- en结尾,即en前面有任意个字母,en后面没有任何字母。
- 组合起来就是`\w*?en\w*?`。
- 将两点结合在一起,得到最后的正则:`\b\w*?en\w*?\b`。
```python
import re
s = '''my parents pay special attention to my education. since I was three years old, they start
to hire a tutor to teach me english. as the saying that interest is the best teacher, indeed, i
listen to a lot of english songs and i am so eager to learn more about the world. mastering
english is the key to know more about the world.'''
pattern_obj = re.compile(r"\b\w*?en\w*?\b")
res = pattern_obj.findall(s)
print(res) # ['parents', 'attention', 'english', 'listen', 'english', 'english']
- 示例3:用\B找出单词中间有en的单词。
- en在中间还指en前后都有任意个字母,且en与这些字母是连续的(\B)。
- 故可以将示例2中的正则改写成:
\b\w*?\Ben\B\w*?\b
```python import re
s = ‘’’my parents pay special attention to my education. since I was three years old, they start to hire a tutor to teach me english. as the saying that interest is the best teacher, indeed, i listen to a lot of english songs and i am so eager to learn more about the world. mastering english is the key to know more about the world.’’’
pattern_obj = re.compile(r”\b\w?\Ben\B\w?\b”) res = pattern_obj.findall(s) print(res) # [‘parents’, ‘attention’]
<a name="WXFTz"></a>
### 3.7 正则的切割与替换
<a name="VMhi2"></a>
#### 3.7.1 split()分割字符串
- 使用字符串的split()函数分割字符串时只能指定一种分隔符。
- 如用字符串的split()函数提取文本中所有单词时,一般只能指定分割符为空格,此时一句话中最后一个单词就会连着句号`.`一起被切出来。
```python
s = '''my parents pay special attention to my education. since I was three years old, they start
to hire a tutor to teach me english. as the saying that interest is the best teacher, indeed, i
listen to a lot of english songs and i am so eager to learn more about the world. mastering
english is the key to know more about the world.'''
words = s.split()
print(words) # ['my', 'parents', 'pay', 'special', 'attention', 'to', 'my', 'education.', 'since', 'I', 'was', 'three', 'years', 'old,', 'they', 'start', 'to', 'hire', 'a', 'tutor', 'to', 'teach', 'me', 'english.', 'as', 'the', 'saying', 'that', 'interest', 'is', 'the', 'best', 'teacher,', 'indeed,', 'i', 'listen', 'to', 'a', 'lot', 'of', 'english', 'songs', 'and', 'i', 'am', 'so', 'eager', 'to', 'learn', 'more', 'about', 'the', 'world.', 'mastering', 'english', 'is', 'the', 'key', 'to', 'know', 'more', 'about', 'the', 'world.']
print([w for w in words if w.endswith(".")]) # ['education.', 'english.', 'world.', 'world.']
正则对象.split(原始字符串数据)
:会将正则对象匹配到的内容作为分隔符,将原始字符串数据进行切割。- 这种方式来提取文本中所有单词时,可以将空格、逗号、句号、感叹号、问号等全部设置为分隔符,只要正则匹配上的全部切割掉。 ```python import re
s = ‘’’my parents pay special attention to my education. since I was three years old, they start to hire a tutor to teach me english. as the saying that interest is the best teacher, indeed, i listen to a lot of english songs and i am so eager to learn more about the world. mastering english is the key to know more about the world.’’’
pattern_obj = re.compile(r”\s|[,.!?]”) res = pattern_obj.split(s) print(res) # [‘my’, ‘parents’, ‘pay’, ‘special’, ‘attention’, ‘to’, ‘my’, ‘education’, ‘’, ‘since’, ‘I’, ‘was’, ‘three’, ‘years’, ‘old’, ‘’, ‘they’, ‘start’, ‘to’, ‘hire’, ‘a’, ‘tutor’, ‘to’, ‘teach’, ‘me’, ‘english’, ‘’, ‘as’, ‘the’, ‘saying’, ‘that’, ‘interest’, ‘is’, ‘the’, ‘best’, ‘teacher’, ‘’, ‘indeed’, ‘’, ‘i’, ‘listen’, ‘to’, ‘a’, ‘lot’, ‘of’, ‘english’, ‘songs’, ‘and’, ‘i’, ‘am’, ‘so’, ‘eager’, ‘to’, ‘learn’, ‘more’, ‘about’, ‘the’, ‘world’, ‘’, ‘mastering’, ‘english’, ‘is’, ‘the’, ‘key’, ‘to’, ‘know’, ‘more’, ‘about’, ‘the’, ‘world’, ‘’]
从结果中可以看出存在一些空字符串,需要过滤以下
res_end = [w for w in res if w != “”] print(res_end) # [‘my’, ‘parents’, ‘pay’, ‘special’, ‘attention’, ‘to’, ‘my’, ‘education’, ‘since’, ‘I’, ‘was’, ‘three’, ‘years’, ‘old’, ‘they’, ‘start’, ‘to’, ‘hire’, ‘a’, ‘tutor’, ‘to’, ‘teach’, ‘me’, ‘english’, ‘as’, ‘the’, ‘saying’, ‘that’, ‘interest’, ‘is’, ‘the’, ‘best’, ‘teacher’, ‘indeed’, ‘i’, ‘listen’, ‘to’, ‘a’, ‘lot’, ‘of’, ‘english’, ‘songs’, ‘and’, ‘i’, ‘am’, ‘so’, ‘eager’, ‘to’, ‘learn’, ‘more’, ‘about’, ‘the’, ‘world’, ‘mastering’, ‘english’, ‘is’, ‘the’, ‘key’, ‘to’, ‘know’, ‘more’, ‘about’, ‘the’, ‘world’]
<a name="R1Ufm"></a>
#### 3.7.2 sub()替换目标子字符串
- 字符串中也有替换操作,但字符串中的替换操作只能替换指定内容的目标字符串,不能替换指定格式的目标字符串。
```python
s = "你好110193287"
print(s.replace("1", "*")) # 你好**0*93287
正则对象.sub(新字符串, 原始字符串数据)
:根据正则对象在原始字符串数据中匹配数据,若匹配成功,则将匹配到的所有子字符串替换成新字符串。- 示例:将字符串
s = "你好110193287Hello110193287"
中所有的数字字符替换成*
。 ```python import re
s = “你好110193287Hello110193287” pattern_obj = re.compile(r”\d”) res = pattern_obj.sub(““, s) print(res) # 你好**Hello*
- sub()函数中的新字符串可以是一个固定的字符串,也可以是一个函数,在函数中接收正则对象匹配到的Match对象,并设置转换规则,其返回值就是要替换的内容。
- 示例:将字符串`s = "你好110193287"`中所有的数字替换成其对应的中文大写形式。
```python
import re
def replace(match_obj):
"""
:param match_obj: 正则对象匹配到的Match对象。
:return: 要替换的内容。
"""
transfer_dic = {
"1": "壹",
"2": "贰",
"3": "参",
"4": "肆",
"5": "伍",
"6": "陆",
"7": "柒",
"8": "捌",
"9": "玖",
"0": "零"
} # 转换规则,键正则匹配到的内容,值为要替换的目标内容。
return transfer_dic[match_obj.group()] # 获取Match对象的内容,获取其需要转换的目标内容并返回。
s = "你好110193287"
pattern_obj = re.compile(r"\d") # 匹配所有数字的正则对象
res = pattern_obj.sub(replace, s) # 传入的是转换函数与原始字符串数据,而非调用转换函数得到的结果。
print(res) # 你好壹壹零壹玖参贰捌柒
3.8 匹配模式
- 在3.2.1中有提到,一个完整的compile函数写法为:
re.compile(正则表达式[, 匹配模式])
。 - 其中的匹配模式中一些常用的匹配模式如下:
re.I
:进行匹配时忽略大小写。 ```python import re
pattern_obj_1 = re.compile(r”hel”) res1 = pattern_obj_1.match(“Hello”) print(res1) # None,说明正常情况下正则匹配是区分大小写的。
pattern_obj_2 = re.compile(r”hel”, re.I)
res2 = pattern_obj_2.match(“Hello”)
print(res2) #
- `re.M`:多行匹配,影响的是多行文本中每一行以什么开头、以什么结尾的内容。
- 正常情况下多行文本是没有办法定位到每一行的开头和结尾的。
- 若非要匹配每一行的开头和结尾,就要将匹配模式设置为`re.M`。
```python
import re
s = '''hello nice to meet you
happy birthday to you
good night
i am fine
hi my name is hanmeimei
'''
pattern_obj_1 = re.compile(r"^h.+")
res1 = pattern_obj_1.findall(s)
print(res1) # ['hello nice to meet you']。
# 没有匹配到happy birthday to you和hi my name is hanmeimei,说明这种情况下只能匹配一行。
pattern_obj_2 = re.compile(r"^h.+", re.M)
res2 = pattern_obj_2.findall(s)
print(res2) # ['hello nice to meet you', 'happy birthday to you', 'hi my name is hanmeimei']
re.S
:让.
匹配\n
。- 在2.2.1中有提到,
.
可以匹配任意单个字符,但不匹配换行符\n
。 - 若要让
.
匹配\n
,则需要将匹配模式设置为re.S
。 ```python import re
- 在2.2.1中有提到,
s = ‘
pattern_obj_2 = re.compile(r”
<a name="lHRUl"></a>
### 3.9 一道面试题
- 题目:问下列程序的运行结果。
```python
import re
print(re.sub(".*", "+", "asd asd dj"))
print(re.sub(".+", "+", "asd asd dj"))
答案:
++
+
分析:+号匹配的是出现大于等于一次的字符,故
.+
就是将一整个字符串当成一个整体,故第二个print只输出一个+;号匹配的是出现零次或多次的字符,`.`会先匹配整个字符串,替换成一个+号,接着它还会匹配一个空字符,也替换成+号,故第一个print有两个+。