第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地址。
请使用正则表达式设计你的代码。
"""
  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

测试划水老师傅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值