为什么 re.sub 会替换整个模式,而不仅仅是其中的一个捕获组?
Why does re.sub replace the entire pattern, not just a capturing group within it?
re.sub('a(b)','d','abc')
产生 dc
,而不是 adc
。
为什么 re.sub
替换整个捕获组,而不只是捕获组'(b)'?
因为它应该替换整个出现的模式:
Return the string obtained by replacing the leftmost non-overlapping occurrences of the pattern in string by the replacement repl.
如果只替换一些子组,那么包含多个组的复杂正则表达式将无法工作。有几种可能的解决方案:
- 完整指定模式:
re.sub('ab', 'ad', 'abc')
- 我最喜欢的,因为它非常易读且明确。
- 捕获您想要保留的组,然后在模式中引用它们(注意它应该是原始字符串以避免转义):
re.sub('(a)b', r'd', 'abc')
- 与之前的选项类似:提供回调函数作为
repl
参数并使其处理 Match
对象和 return 所需的结果。
- 使用lookbehinds/lookaheds,不包含在匹配中,但影响匹配:
re.sub('(?<=a)b', r'd', 'abxb')
产生adxb
。组开头的 ?<=
表示 "it's a lookahead"。
import re
pattern = re.compile(r"I am (\d{1,2}) .*", re.IGNORECASE)
text = "i am 32 years old"
if re.match(pattern, text):
print(
re.sub(pattern, r"Your are years old.", text, count=1)
)
同上,首先我们编译一个带有不区分大小写标志的正则表达式模式。
然后我们检查文本是否与模式匹配,如果匹配,我们引用正则表达式模式 (age) 中唯一组号为 \1 的组。
因为这正是 re.sub()
文档告诉您它应该做的事情:
- 模式
'a(b)'
表示“匹配 'a',带有可选的尾随 'b'”。 (它可以自己匹配 'a',但它不可能像你期望的那样自己匹配 'b'。如果你是那个意思,请使用 non-greedy (a)??b
).
- replacement-string 是 'd'
- 因此在您的字符串 'abc' 上,它匹配所有 'ab' 并将其替换为 'd',因此结果为 'dc'
如果你想要你想要的输出,你需要在 '(a)??'
上进行 non-greedy 匹配:
>>> re.sub('(a)??b','d','abc')
'dc'
我知道这不是严格回答 OP 问题,但这个问题可能很难 google(被 \1 解释淹没...)
对于那些像我一样的人来到这里是因为他们想用字符串替换不是第一个的捕获组,而不需要对字符串或正则表达式有特殊了解:
#find offset [start, end] of a captured group within string
r = regex.search(oldText).span(groupNb)
#slice the old string and insert replacementText in the middle
newText = oldText[:r[0]] + replacementText + oldText[r[1]:]
我知道这是想要的行为,但我仍然不明白为什么 re.sub 不能指定它应该替换的实际捕获组...
re.sub('a(b)','d','abc')
产生 dc
,而不是 adc
。
为什么 re.sub
替换整个捕获组,而不只是捕获组'(b)'?
因为它应该替换整个出现的模式:
Return the string obtained by replacing the leftmost non-overlapping occurrences of the pattern in string by the replacement repl.
如果只替换一些子组,那么包含多个组的复杂正则表达式将无法工作。有几种可能的解决方案:
- 完整指定模式:
re.sub('ab', 'ad', 'abc')
- 我最喜欢的,因为它非常易读且明确。 - 捕获您想要保留的组,然后在模式中引用它们(注意它应该是原始字符串以避免转义):
re.sub('(a)b', r'd', 'abc')
- 与之前的选项类似:提供回调函数作为
repl
参数并使其处理Match
对象和 return 所需的结果。 - 使用lookbehinds/lookaheds,不包含在匹配中,但影响匹配:
re.sub('(?<=a)b', r'd', 'abxb')
产生adxb
。组开头的?<=
表示 "it's a lookahead"。
import re
pattern = re.compile(r"I am (\d{1,2}) .*", re.IGNORECASE)
text = "i am 32 years old"
if re.match(pattern, text):
print(
re.sub(pattern, r"Your are years old.", text, count=1)
)
同上,首先我们编译一个带有不区分大小写标志的正则表达式模式。
然后我们检查文本是否与模式匹配,如果匹配,我们引用正则表达式模式 (age) 中唯一组号为 \1 的组。
因为这正是 re.sub()
文档告诉您它应该做的事情:
- 模式
'a(b)'
表示“匹配 'a',带有可选的尾随 'b'”。 (它可以自己匹配 'a',但它不可能像你期望的那样自己匹配 'b'。如果你是那个意思,请使用 non-greedy(a)??b
). - replacement-string 是 'd'
- 因此在您的字符串 'abc' 上,它匹配所有 'ab' 并将其替换为 'd',因此结果为 'dc'
如果你想要你想要的输出,你需要在 '(a)??'
上进行 non-greedy 匹配:
>>> re.sub('(a)??b','d','abc')
'dc'
我知道这不是严格回答 OP 问题,但这个问题可能很难 google(被 \1 解释淹没...)
对于那些像我一样的人来到这里是因为他们想用字符串替换不是第一个的捕获组,而不需要对字符串或正则表达式有特殊了解:
#find offset [start, end] of a captured group within string
r = regex.search(oldText).span(groupNb)
#slice the old string and insert replacementText in the middle
newText = oldText[:r[0]] + replacementText + oldText[r[1]:]
我知道这是想要的行为,但我仍然不明白为什么 re.sub 不能指定它应该替换的实际捕获组...