实现正则替换的函数有re.sub和re.subn,它们参数均是:

  1. re.sub*(pattern, repl, string, count=0, flags=0)

参数:

  • pattern : 正则中的模式字符串。
  • repl : 替换的表达式,也可为一个函数。
  • string : 要被替换的原始字符串。
  • count : 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。

re.subn相对re.sub的区别是会在re.sub返回结果的基础上额外返回替换次数。

基本替换

有一个电话号码带注释的字符串:

  1. phone = "2004-959-559 # 这是一个电话号码"

如果我们需要删除注释:

  1. num = re.sub(r'#.*$', "", phone)
  2. print("电话号码:", num)

结果:

  1. 电话号码: 2004-959-559

删除所有非数字的内容:

  1. num = re.sub(r'\D', "", phone)
  2. print("电话号码:", num)

结果:

  1. 电话号码: 2004959559

环视替换

给金额添加万分符。
有一个金额字符串:

  1. s = """5305256725元
  2. 4220元
  3. 870元
  4. 7866369414527元
  5. 144995元
  6. 2069993310元
  7. 354070715448元
  8. 711元
  9. 2113046206元"""

需要给万亿、亿、万位置添加逗号,例如 7866369414527元 被转换为 7,8663,6941,4527元 。 这些位置均为距离元4n个字符的位置,所以可以这样写:

  1. print(re.sub(r"(?<=\d)(?=(?:\d{4})+元)", ",", s, flags=re.M))

结果:

  1. 53,0525,6725
  2. 4220
  3. 870
  4. 7,8663,6941,4527
  5. 14,4995
  6. 20,6999,3310
  7. 3540,7071,5448
  8. 711
  9. 21,1304,6206

(?<=\d) 表示左边必须是一个数字, (?=(?:\d{4})+元) 表示右边必须是紧挨元,而且是4n个数字。 这样在对应的位置替换成 , ,便添加了万分符。

repl替换表达式引用分组

\ 表示引用编号为 \ 的分组匹配到的字符串,这个规则不仅可以在匹配表达式中使 用,还可以在替换表达式中使用。
需求1:将重叠的字符替换成单个字符(zzzz->z)
例如,将

“我我…我我…我要..要要…要要…学学学….学学…编编编…编程..程.程程…程…程”

转成:

“我要学编程”

思路:

  1. 先将字符 . 去掉。
  2. 再将多个重复的内容变成单个内容。

    1. s = "我我...我我...我要..要要...要要...学学学....学学...编编编...编程..程.程程...
    2. 程...程"
    3. # 去掉所有的字符.
    4. s = s.replace(".", "")
    5. # 连续重复字符转单个字符
    6. s = re.sub(r"(.)\1+", r"\1", s)
    7. s

    结果:

    1. '我要学编程'

    那么如果使用subn方法有什么特别之处呢?

    1. s = "我我...我我...我要..要要...要要...学学学....学学...编编编...编程..程.程程...
    2. 程...程"
    3. # 去掉所有的字符.
    4. s = s.replace(".", "")
    5. print("去掉字符.之后:", s)
    6. # 连续重复字符转单个字符
    7. s, count = re.subn(r"(.)\1+", r"\1", s)
    8. print("结果:", s)
    9. print("替换次数:", count)

    结果:

    1. 去掉字符.之后: 我我我我我要要要要要学学学学学编编编编程程程程程程
    2. 结果: 我要学编程
    3. 替换次数: 5

    可以看到subn方法可以通过元组匹配的方式,额外得到替换的次数,但目前我还没有遇到哪个场景需 要得到这个替换次数,但或许哪天你真需要知道替换多少次的时候,使用subn能省不少事。
    需求2:连续单词去重
    有一篇英文文章,里面有一些单词连续出现了多次,我们认为连续出现多次的单词应该是一次,比如:

    the little cat cat is in the hat hat hat2, we like it.

    其中 cat 和 hat 连接出现多次,要求处理后结果是:

    1. the little cat is in the hat hat2, we like it.
    1. s = "the little cat cat is in the hat hat hat2, we like it."
    2. re.sub(r"(\b\w+)(?:\s+\1\b)+", r"\1", s)

    结果:

    1. 'the little cat is in the hat hat2, we like it.'

    需求2:将ip地址进行地址段顺序的排序。
    有一个ip字符串:

    1. s = "192.68.1.254 102.49.23.013 10.10.10.10 2.2.2.2 8.109.90.30"

    现在需要让它内部的每个ip排序输出。
    思路:

  3. 按照每一段需要的最多的0进行补齐,那么每一段就会至少保证有3位。

  4. 将每一段只保留3位。这样,所有的ip地址都是每一段3位。
  5. 切割排序,并拼接排好序的ip地址字符串
  6. 去掉结果字符串每个数字开头的0
    1. s = "192.68.1.254 102.49.23.013 10.10.10.10 2.2.2.2 8.109.90.30"
    2. # 1. 按照每一段需要的最多的0进行补齐,那么每一段就会至少保证有3位。
    3. s = re.sub(r"(\d+)", r"00\1", s)
    4. print("每段开头补2个0后:", s)
    5. # 2. 将每一段只保留3位。这样,所有的ip地址都是每一段3位。
    6. s = re.sub(r"0*(\d{3})", r"\1", s)
    7. print("每段仅保留3个数字:", s)
    8. # 3. 切割排序,并拼接排好序的ip地址字符串
    9. s = " ".join(sorted(s.split()))
    10. print("切割排序并拼接后:", s)
    11. # 4. 去掉结果字符串每个数字开头的0
    12. s = re.sub(r"0*(\d+)", r"\1", s)
    13. print("最终结果:", s)
    结果:
    1. 每段开头补20后: 00192.0068.001.00254 00102.0049.0023.00013
    2. 0010.0010.0010.0010 002.002.002.002 008.00109.0090.0030
    3. 每段仅保留3个数字: 192.068.001.254 102.049.023.013 010.010.010.010
    4. 002.002.002.002 008.109.090.030
    5. 切割排序并拼接后: 002.002.002.002 008.109.090.030 010.010.010.010
    6. 102.049.023.013 192.068.001.254
    7. 最终结果: 2.2.2.2 8.109.90.30 10.10.10.10 102.49.23.13 192.68.1.254
    可以看到最终实现了,ip地址的排序。
    repl替换表达式使用函数
    repl 替换表达式,也可传入一个函数,这个函数的参数必须是一个 re.MatchObject 对象(参看前面的 正则匹配部分)。 在其他编程语言中,正则表达式的替换功能,往往都是不支持传入函数的,导致要实现一些数值计算性 的功能代码会变得比较复杂,而python的正则替换由于支持函数,所以可以很简单的实现一些较为复杂 的逻辑。 先从简单的例子开始:
    示例1:数值翻倍 假如我们有一个字符串:
    1. s = 'A23G4HFD567'
    希望将这个字符串所有连续的数字都翻倍,使用正则替换传入函数会非常方便:
    1. s = 'A23G4HFD567'
    2. print(re.sub('\d+', lambda m: str(int(m.group(0))*2), s))
    假如python的正则替换不支持传入函数就会相对比较复杂:
    1. buff = list(s)
    2. for m in re.finditer("\d+", s):
    3. pos = m.span()
    4. buff[pos[0]:pos[1]] = list(str(int(m.group(0))*2))
    5. s = "".join(buff)
    6. s
    结果均为:
    1. 'A46G8HFD1134
    示例2:数值隔断 有一个字符串:
    1. s='AB837D5D4F7G8H7F8H56D4D7G4D3'
    想将这个字符串所有>=6的单个数字替换成9, <6的单个数字替换为0:
    1. s = 'AB837D5D4F7G8H7F8H56D4D7G4D3'
    2. print(re.sub('\d', lambda m:'9' if int(m.group()) >= 6 else '0', s))
    结果:
    1. AB909D0D0F9G9H9F9H09D0D9G0D0
    示例3:顺序编号
    有一段字符串 “a,b,c,d,e,f” ,我们希望将每一个出现的字母增加一个递增的编号,比如:
    1. s = "a,b,c,d,e,f"
    希望得到结果: ‘1.a,2.b,3.c,4.d,5.e,6.f’
    使用编号迭代器+正则替换会变得非常简单:
    1. s = "a,b,c,d,e,f"
    2. numbers = iter(range(1, 10000))
    3. re.sub("\w", lambda m: f"{next(numbers)}.{m.group()}", s)
    结果:
    1. '1.a,2.b,3.c,4.d,5.e,6.f'
    我们使用的编号不可能超过1万,所以range函数的最大值传入10000即可,iter获取了range对象的迭 代器,从而得到了一个可以不断获取下一个编号的编号迭代器。 顺序编号的比较典型应用场景是模板数据回传,举个比较傻的例子: 比如有一个含有很多sql查询语句的大文本,我们需要将其中所有的sql语句提取出来,执行完毕,再将 查询结果写回到sql语句原本所在的位置。 要实现这个功能,首先可以先将所有的sql语句都替换成顺序编号的占位符,我们就以下面这个比较简 单的文本为例吧:
    1. s="""
    2. -- 一个查询
    3. SELECT
    4. CNO,
    5. CNAME
    6. FROM
    7. COURSES
    8. WHERE CREDIT = 3;
    9. -- 例 查询年龄大于 22 岁的学生情况。
    10. SELECT
    11. *
    12. FROM
    13. STUDENTS
    14. WHERE AGE > 22 ;
    15. -- 例 找出籍贯为河北的男生的姓名和年龄。
    16. SELECT
    17. SNAME,
    18. AGE
    19. FROM
    20. STUDENTS
    21. WHERE BPLACE = ' 河北 '
    22. AND SEX = ' 男 ' ;"""
    只需执行:
    1. numbers = iter(range(10000))
    2. template_text = re.sub("^select .+?;", lambda m: "{%d}" % next(numbers), s,
    3. flags=re.I | re.M | re.S)
    4. print(template_text)
    结果:
    1. -- 一个查询
    2. {0}
    3. -- 查询年龄大于 22 岁的学生情况。
    4. {1}
    5. -- 找出籍贯为河北的男生的姓名和年龄。
    6. {2}