import re

s = "WWWRRBBBWW"
s, n = re.subn(r"(.)\1{2,}", "", s)
s, n # ('RRWW', 2)

r"(.)\1{2,}" 
# . 任意字符,除 \n 以外。(.)\1 反向引用第一个小括号,内容相同,重复一次。
# {n,} 至少 n 次 re.findall(‘cb{2,}’,‘bchcbfbcbb’)结果[‘cbb’]

Grammar:
re.sub(pattern, repl, string[, count])
使用 repl 替换 string 中每一个匹配的子串后返回替换后的字符串。
当 repl 是一个字符串时,可以使用 \id 或 \g、\g 引用分组,但不能使用编号 0。
当 repl 是一个方法时,这个方法应当只接受一个参数(Match对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。
count 用于指定最多替换次数,不指定时全部替换。

re.subn(pattern, repl, string[, count])
返回 (sub(repl, string[, count]), 替换次数)。

Case:
import re

str = "https://i.cnb1logs.co2m/Edi3tPosts.asp4x?opt=999"

pattern = re.compile(r'(\.)')
print('\.     :', re.sub(pattern, '-', str))

pattern = re.compile(r'\/([^*]+)\/')
print('\/([^*]+)\/ :', re.sub(pattern, r'<em>\1<em>', str))

pattern = re.compile(r'(\w+)(\w+)(\d+)')
#先切片测试
print(re.split(pattern, str))
print(re.sub(pattern, r'\3 \1', str))
#subn统计sub替换次数
print(re.subn(pattern, r'\3 \1', str))

Output
\.     : https://i-cnb1logs-co2m/Edi3tPosts-asp4x?opt=999
\/([^*]+)\/ : https:<em>/i.cnb1logs.co2m<em>Edi3tPosts.asp4x?opt=999
['https://i.', 'cn', 'b', '1', 'logs.', 'c', 'o', '2', 'm/', 'Ed', 'i', '3', 'tPosts.', 'as', 'p', '4', 'x?opt=', '9', '9', '9', '']
https://i.1 cnlogs.2 cm/3 EdtPosts.4 asx?opt=9 9
('https://i.1 cnlogs.2 cm/3 EdtPosts.4 asx?opt=9 9', 5)

在正则表达式中

  • ‘\1’ 匹配的是 字符 ‘\1’ 。 (因为 ‘\’ 匹配字符 ‘\’ )
  • ‘\2’ 匹配的是 字符 ‘\2’

单独斜杠的 \1 , \2 就是反向引用了。

  • ‘\1’ 匹配的是 所获取的第1个()匹配的引用。例如,’(\d)\1’ 匹配两个连续数字字符。如33aa 中的33
  • ‘\2’ 匹配的是 所获取的第2个()匹配的引用。

例如,’(\d)(a)\1’ 匹配第一是数字第二是字符 a,第三 \1 必须匹配第一个一样的数字重复一次,也就是被引用一次。如 “9a9” 被匹配,但 “9a8” 不会被匹配,因为第三位的 \1 必须是 9 才可以,‘(\d)(a)\2’ 匹配第一个是一个数字,第二个是a,第三个 \2 必须是第二组()中匹配一样的,如,“8aa” 被匹配,但 “8ab”,“7a7” 不会被匹配,第三位必须是第二组字符的复制版,也是就引用第二组正则的匹配内容。