实现正则替换的函数有re.sub和re.subn,它们参数均是:
re.sub*(pattern, repl, string, count=0, flags=0)
参数:
- pattern : 正则中的模式字符串。
- repl : 替换的表达式,也可为一个函数。
- string : 要被替换的原始字符串。
- count : 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。
re.subn相对re.sub的区别是会在re.sub返回结果的基础上额外返回替换次数。
基本替换
有一个电话号码带注释的字符串:
phone = "2004-959-559 # 这是一个电话号码"
如果我们需要删除注释:
num = re.sub(r'#.*$', "", phone)
print("电话号码:", num)
结果:
电话号码: 2004-959-559
删除所有非数字的内容:
num = re.sub(r'\D', "", phone)
print("电话号码:", num)
结果:
电话号码: 2004959559
环视替换
给金额添加万分符。
有一个金额字符串:
s = """5305256725元
4220元
870元
7866369414527元
144995元
2069993310元
354070715448元
711元
2113046206元"""
需要给万亿、亿、万位置添加逗号,例如 7866369414527元 被转换为 7,8663,6941,4527元 。 这些位置均为距离元4n个字符的位置,所以可以这样写:
print(re.sub(r"(?<=\d)(?=(?:\d{4})+元)", ",", s, flags=re.M))
结果:
53,0525,6725元
4220元
870元
7,8663,6941,4527元
14,4995元
20,6999,3310元
3540,7071,5448元
711元
21,1304,6206元
(?<=\d) 表示左边必须是一个数字, (?=(?:\d{4})+元) 表示右边必须是紧挨元,而且是4n个数字。 这样在对应的位置替换成 , ,便添加了万分符。
repl替换表达式引用分组
\ 表示引用编号为 \ 的分组匹配到的字符串,这个规则不仅可以在匹配表达式中使 用,还可以在替换表达式中使用。
需求1:将重叠的字符替换成单个字符(zzzz->z)
例如,将
“我我…我我…我要..要要…要要…学学学….学学…编编编…编程..程.程程…程…程”
转成:
“我要学编程”
思路:
- 先将字符 . 去掉。
再将多个重复的内容变成单个内容。
s = "我我...我我...我要..要要...要要...学学学....学学...编编编...编程..程.程程...
程...程"
# 去掉所有的字符.
s = s.replace(".", "")
# 连续重复字符转单个字符
s = re.sub(r"(.)\1+", r"\1", s)
s
结果:
'我要学编程'
那么如果使用subn方法有什么特别之处呢?
s = "我我...我我...我要..要要...要要...学学学....学学...编编编...编程..程.程程...
程...程"
# 去掉所有的字符.
s = s.replace(".", "")
print("去掉字符.之后:", s)
# 连续重复字符转单个字符
s, count = re.subn(r"(.)\1+", r"\1", s)
print("结果:", s)
print("替换次数:", count)
结果:
去掉字符.之后: 我我我我我要要要要要学学学学学编编编编程程程程程程
结果: 我要学编程
替换次数: 5
可以看到subn方法可以通过元组匹配的方式,额外得到替换的次数,但目前我还没有遇到哪个场景需 要得到这个替换次数,但或许哪天你真需要知道替换多少次的时候,使用subn能省不少事。
需求2:连续单词去重
有一篇英文文章,里面有一些单词连续出现了多次,我们认为连续出现多次的单词应该是一次,比如:the little cat cat is in the hat hat hat2, we like it.
其中 cat 和 hat 连接出现多次,要求处理后结果是:
the little cat is in the hat hat2, we like it.
s = "the little cat cat is in the hat hat hat2, we like it."
re.sub(r"(\b\w+)(?:\s+\1\b)+", r"\1", s)
结果:
'the little cat is in the hat hat2, we like it.'
需求2:将ip地址进行地址段顺序的排序。
有一个ip字符串:s = "192.68.1.254 102.49.23.013 10.10.10.10 2.2.2.2 8.109.90.30"
现在需要让它内部的每个ip排序输出。
思路:按照每一段需要的最多的0进行补齐,那么每一段就会至少保证有3位。
- 将每一段只保留3位。这样,所有的ip地址都是每一段3位。
- 切割排序,并拼接排好序的ip地址字符串
- 去掉结果字符串每个数字开头的0
结果:s = "192.68.1.254 102.49.23.013 10.10.10.10 2.2.2.2 8.109.90.30"
# 1. 按照每一段需要的最多的0进行补齐,那么每一段就会至少保证有3位。
s = re.sub(r"(\d+)", r"00\1", s)
print("每段开头补2个0后:", s)
# 2. 将每一段只保留3位。这样,所有的ip地址都是每一段3位。
s = re.sub(r"0*(\d{3})", r"\1", s)
print("每段仅保留3个数字:", s)
# 3. 切割排序,并拼接排好序的ip地址字符串
s = " ".join(sorted(s.split()))
print("切割排序并拼接后:", s)
# 4. 去掉结果字符串每个数字开头的0
s = re.sub(r"0*(\d+)", r"\1", s)
print("最终结果:", s)
可以看到最终实现了,ip地址的排序。每段开头补2个0后: 00192.0068.001.00254 00102.0049.0023.00013
0010.0010.0010.0010 002.002.002.002 008.00109.0090.0030
每段仅保留3个数字: 192.068.001.254 102.049.023.013 010.010.010.010
002.002.002.002 008.109.090.030
切割排序并拼接后: 002.002.002.002 008.109.090.030 010.010.010.010
102.049.023.013 192.068.001.254
最终结果: 2.2.2.2 8.109.90.30 10.10.10.10 102.49.23.13 192.68.1.254
repl替换表达式使用函数
repl 替换表达式,也可传入一个函数,这个函数的参数必须是一个 re.MatchObject 对象(参看前面的 正则匹配部分)。 在其他编程语言中,正则表达式的替换功能,往往都是不支持传入函数的,导致要实现一些数值计算性 的功能代码会变得比较复杂,而python的正则替换由于支持函数,所以可以很简单的实现一些较为复杂 的逻辑。 先从简单的例子开始:
示例1:数值翻倍 假如我们有一个字符串:
希望将这个字符串所有连续的数字都翻倍,使用正则替换传入函数会非常方便:s = 'A23G4HFD567'
假如python的正则替换不支持传入函数就会相对比较复杂:s = 'A23G4HFD567'
print(re.sub('\d+', lambda m: str(int(m.group(0))*2), s))
结果均为:buff = list(s)
for m in re.finditer("\d+", s):
pos = m.span()
buff[pos[0]:pos[1]] = list(str(int(m.group(0))*2))
s = "".join(buff)
s
示例2:数值隔断 有一个字符串:'A46G8HFD1134
想将这个字符串所有>=6的单个数字替换成9, <6的单个数字替换为0:s='AB837D5D4F7G8H7F8H56D4D7G4D3'
结果:s = 'AB837D5D4F7G8H7F8H56D4D7G4D3'
print(re.sub('\d', lambda m:'9' if int(m.group()) >= 6 else '0', s))
示例3:顺序编号AB909D0D0F9G9H9F9H09D0D9G0D0
有一段字符串 “a,b,c,d,e,f” ,我们希望将每一个出现的字母增加一个递增的编号,比如:
希望得到结果: ‘1.a,2.b,3.c,4.d,5.e,6.f’s = "a,b,c,d,e,f"
使用编号迭代器+正则替换会变得非常简单:
结果:s = "a,b,c,d,e,f"
numbers = iter(range(1, 10000))
re.sub("\w", lambda m: f"{next(numbers)}.{m.group()}", s)
我们使用的编号不可能超过1万,所以range函数的最大值传入10000即可,iter获取了range对象的迭 代器,从而得到了一个可以不断获取下一个编号的编号迭代器。 顺序编号的比较典型应用场景是模板数据回传,举个比较傻的例子: 比如有一个含有很多sql查询语句的大文本,我们需要将其中所有的sql语句提取出来,执行完毕,再将 查询结果写回到sql语句原本所在的位置。 要实现这个功能,首先可以先将所有的sql语句都替换成顺序编号的占位符,我们就以下面这个比较简 单的文本为例吧:'1.a,2.b,3.c,4.d,5.e,6.f'
只需执行:s="""
-- 一个查询
SELECT
CNO,
CNAME
FROM
COURSES
WHERE CREDIT = 3;
-- 例 查询年龄大于 22 岁的学生情况。
SELECT
*
FROM
STUDENTS
WHERE AGE > 22 ;
-- 例 找出籍贯为河北的男生的姓名和年龄。
SELECT
SNAME,
AGE
FROM
STUDENTS
WHERE BPLACE = ' 河北 '
AND SEX = ' 男 ' ;"""
结果:numbers = iter(range(10000))
template_text = re.sub("^select .+?;", lambda m: "{%d}" % next(numbers), s,
flags=re.I | re.M | re.S)
print(template_text)
-- 一个查询
{0}
-- 例 查询年龄大于 22 岁的学生情况。
{1}
-- 例 找出籍贯为河北的男生的姓名和年龄。
{2}