「Python编程基础」第8章:处理文本的神器“正则表达式”
文章目录
一、处理文本的神器
假设公司仅给你1天的时间,需要将一篇10万字文本中的某十几个邮箱修改成指定邮箱,你该怎么办?
也许你会用Ctrl-F快速搜索。
但假设有些邮箱不需要修改,有些文本数字又和这些邮箱号码一样。
请问阁下又如何面对?
面对这一头疼的问题,还仅仅只给一天的时间,我想大部分人都要彻夜难眠了。
而Python就很好的给我们解决了这一问题。
当你写好一个def(函数)时,面对这一问题,仅仅只需要几分钟(编写匹配规则,运行函数),就能解决。
二、不用正则表达式,如何查找?
假设,你希望在字符串中,查找所有180开头的电话号码。比如“18000000000”。
def is_phone_number(text):
if len(text) != 11:
return False
for i in range(0, 11):
if not text[i].isdecimal():
return False
for i in range(0, 3):
if text[0] != '1' or text[1] != '8' or text[2] != '0':
return False
return True
message = '手机号:18000000000,18000000001,17000000000'
for i in range(len(message)):
chunk = message[i:i + 11]
if is_phone_number(chunk):
print(f'找到180开头的手机号:{chunk}')
print("程序运行结束!")
说实话,我第一次写教程,我竟然发现不用正则表达式,会这么恶心。。。
三、用正则表达式查找文本模式
接下来,我们就试试用正则表达式来找找看。
# 1.导入正则表达式模块
import re
#2. 创建正则表达式匹配规则
is_phone_number = re.compile(r'180\d\d\d\d\d\d\d\d')
#3. 待匹配的字符串
search_phone = is_phone_number.search('手机号:18000000000,18000000001,17000000000')
#4. 打印结果
print(f'找到180开头的手机号:{search_phone.group()}')
我们发现,这要比不用正则表达式的方法简单很多。
如果你们运行了上面这个代码,你会发现一个小小的问题。
问题的产生,就是因为search()只匹配一次,所以re模块还为我们提供了一个findall()的方法。
import re
is_phone_number = re.compile(r'180\d\d\d\d\d\d\d\d')
search_phone = is_phone_number.findall('手机号:18000000000,18000000001,17000000000')
for i in search_phone:
print(i)
你会发现,当你要找出多个内容时,使用findall()是一个不错的选择。
四、用?号实现可选匹配
有时候,我们想匹配是可选的。
str_1 = "我是测试划水老师傅,记得点个关注!"
str_1 = "我是测试老师傅,记得点个关注!"
"""
我想用一个正则表达式,找出包含“测试**老师傅”的字符,该如何做?
"""
你可以用?号来实现匹配
regex = re.compile(r'测试(划水)?老师傅')
regex_str_1 = regex.search("我是测试划水老师傅,记得点个关注!").group()
print(regex_str_1)
regex_str_2 = regex.search("我是测试老师傅,记得点个关注!").group()
print(regex_str_2)
"""运行结果:
测试划水老师傅
测试老师傅
"""
(划水)? 代表“划水”时可选的,可以出现0次,也可以出现1次,这就是为什么我可以用同一个表达式,找出我期望的字符串。
五、用*号实现匹配0次或多次
假设我的名字中间的“划水”出现多次,而我想要匹配多次“划水”这个名词。
那刚才提到的正则就无法正常匹配。
import re
str_1 = "我是测试划水划水划水老师傅,记得点个关注!"
regex = re.compile(r'测试(划水)?老师傅')
regex_str_1 = regex.search("我是测试划水老师傅,记得点个关注!").group()
print(regex_str_1)
如果你运行了代码,会发现结果如下:
测试划水老师傅
但是与我预期不符,我想要匹配到的是“测试划水划水划水老师傅”!
修改正则匹配规则如下:
regex = re.compile(r'测试(划水)*老师傅')
regex_str_1 = regex.search("我是测试划水划水划水老师傅,记得点个关注!").group()
print(regex_str_1)
代码运行结果:
测试划水划水划水老师傅
六、用+号匹配一次或多次
与*号不同的是,+号要求被匹配的字符,至少出现一次,所以当没有匹配到的时候,会返回None。
import re
regex = re.compile(r'测试(划水)+老师傅')
regex_str_1 = regex.search("我是测试划水划水划水老师傅,记得点个关注!").group()
regex_str_2 = regex.search("测试老师傅,记得点个关注!")
print(regex_str_1)
print(regex_str_2)
"""运行结果:
测试划水划水划水老师傅
None
"""
七、用花括号匹配特定次数
import re
regex = re.compile(r'测试(划水){2}老师傅')
regex_str_1 = regex.search("我是测试划水划水划水老师傅,记得点个关注!")
regex_str_2 = regex.search("测试划水划水老师傅,记得点个关注!")
print(regex_str_1)
print(regex_str_2.group())
"""运行结果:
None
测试划水划水老师傅
"""
我们发现,括号内的{2},表示匹配“划水”出现两次,所以,regex_str_1的结果是None。
八、贪心和非贪心匹配
先直接看看代码示例,看看是什么导致输出不一样?
import re
# 贪心匹配
str_1 = "OHOHOHOHOH"
regex = re.compile(r'(OH){3,5}')
regex_str_1 = regex.search(str_1).group()
print(regex_str_1)
"""运行结果:
OHOHOHOHOH
"""
# 非贪心匹配(惰性匹配)
regex = re.compile(r'(OH){3,5}?')
regex_str_2 = regex.search(str_1).group()
print(regex_str_2)
"""运行结果:
OHOHOH
"""
首先解释下{3,5},是指至少匹配3个,最多匹配5个。
Python的正则表达式式默认贪心匹配,表示按照最多匹配数进行匹配,而在花括号之后加上“?”表示非贪心匹配,表示尽可能按照最短的匹配。
九、正则表达式模式
在之前学手机号匹配的代码中,我们用到了\d 表示代表任意数字,也就是说,\d是正则表达式(0|1|2|3|4|5|6|7|8|9)的缩写。
下表列出了正则表达式模式语法中的特殊元素。
如果你使用模式的同时提供了可选的标志参数,某些模式元素的含义会改变。
模式 | 描述 |
---|---|
^ | 匹配字符串的开头 |
$ | 匹配字符串的末尾。 |
. | 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。 |
[…] | 用来表示一组字符,单独列出:[amk] 匹配 ‘a’,‘m’或’k’ |
[^…] | 不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。 |
re* | 匹配0个或多个的表达式。 |
re+ | 匹配1个或多个的表达式。 |
re? | 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式 |
re{ n} | 精确匹配 n 个前面表达式。例如, o{2} 不能匹配 “Bob” 中的 “o”,但是能匹配 “food” 中的两个 o。 |
re{ n,} | 匹配 n 个前面表达式。例如, o{2,} 不能匹配"Bob"中的"o",但能匹配 "foooood"中的所有 o。“o{1,}” 等价于 “o+”。“o{0,}” 则等价于 “o*”。 |
re{ n, m} | 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式 |
a| b | 匹配a或b |
(re) | 对正则表达式分组并记住匹配的文本 |
(?imx) | 正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域。 |
(?-imx) | 正则表达式关闭 i, m, 或 x 可选标志。只影响括号中的区域。 |
(?: re) | 类似 (…), 但是不表示一个组 |
(?imx: re) | 在括号中使用i, m, 或 x 可选标志 |
(?-imx: re) | 在括号中不使用i, m, 或 x 可选标志 |
(?#…) | 注释. |
(?= re) | 前向肯定界定符。如果所含正则表达式,以 … 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。 |
(?! re) | 前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功 |
(?> re) | 匹配的独立模式,省去回溯。 |
\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, 等. | 匹配一个换行符。匹配一个制表符。等 |
\1…\9 | 匹配第n个分组的内容。 |
\10 | 匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。 |
十、正则表达式实例
字符匹配
实例 | 描述 |
---|---|
python | 匹配 “python”. |
字符类
实例 | 描述 |
---|---|
[Pp]ython | 匹配 “Python” 或 “python” |
rub[ye] | 匹配 “ruby” 或 “rube” |
[aeiou] | 匹配中括号内的任意一个字母 |
[0-9] | 匹配任何数字。类似于 [0123456789] |
[a-z] | 匹配任何小写字母 |
[A-Z] | 匹配任何大写字母 |
[a-zA-Z0-9] | 匹配任何字母及数字 |
[^aeiou] | 除了aeiou字母以外的所有字符 |
[^0-9] | 匹配除了数字外的字符 |
特殊字符类
实例 | 描述 |
---|---|
. | 匹配除 “\n” 之外的任何单个字符。要匹配包括 ‘\n’ 在内的任何字符,请使用象 ‘[.\n]’ 的模式。 |
\d | 匹配一个数字字符。等价于 [0-9]。 |
\D | 匹配一个非数字字符。等价于 [^0-9]。 |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 |
\w | 匹配包括下划线的任何单词字符。等价于’[A-Za-z0-9_]'。 |
\W | 匹配任何非单词字符。等价于 ‘[^A-Za-z0-9_]’。 |
十一、用sub()替换文本
Python中的sub()函数是re模块中的一个函数,用于在字符串中替换指定的子字符串。
它的语法如下:
re.sub(pattern, repl, string, count=0, flags=0)
参数说明:
pattern:要查找的子字符串。
repl:要替换的字符串。
string:要在其中进行替换的原始字符串。
count:可选参数,指定替换操作的次数,如果设置为非零值,则只替换指定次数。
flags:可选参数,指定re模块的标志,如re.IGNORECASE等。
这是一个简单的示例代码:
import re
text = "hello world, world is beautiful"
new_text = re.sub("world", "Python", text)
print(new_text) # "hello Python, Python is beautiful"
在这个例子中,sub()函数将字符串"world"替换为"Python",输出结果为"hello Python, Python is beautiful"。
十二、练习题
"""
假设你有一个任务,要在一篇长的网页或者文章中,找出所有手机号和E-mail地址。
请使用正则表达式设计你的代码。
"""
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!