Python 正则表达式转义运算符 \ 在替换和原始字符串中



我不明白 python 正则表达式中的 scape 运算符 \ 与原始字符串的 r' 一起运作的逻辑。 感谢一些帮助。

法典:

import re
text=' esto  .es  10  . er - 12 .23 with [  and.Other ] here is more ; puntuation'
print('text0=',text)
text1 = re.sub(r'(s+)([;:.-])', r'2', text)
text2 = re.sub(r's+.', '.', text)
text3 = re.sub(r's+.', r'.', text)
print('text1=',text1)
print('text2=',text2)
print('text3=',text3)

该理论说: 反斜杠字符 ('\') 表示特殊形式或允许在不调用其特殊含义的情况下使用特殊字符。

就本期末尾提供的链接所解释的那样,r' 表示一个原始字符串,即符号没有特殊含义,它保持不变。

所以在上面的正则表达式中,我希望 text2 和 text3 是不同的,因为替换文本在文本 2 中是".",即句点,而(原则上)文本 3 中的替换文本是 r'.,这是一个原始字符串,即应该出现的字符串、反斜杠和句点。但它们的结果是相同的:

结果是:

text0=  esto  .es  10  . er - 12 .23 with [  and.Other ] here is more ; puntuation
text1=  esto.es  10. er- 12.23 with [  and.Other ] here is more; puntuation
text2=  esto.es  10. er - 12.23 with [  and.Other ] here is more ; puntuation
text3=  esto.es  10. er - 12.23 with [  and.Other ] here is more ; puntuation
#text2=text3 but substitutions are not the same r'.' vs '.'

在我看来,r' 在替换部分和反斜杠中的工作方式不同。另一方面,我的直觉告诉我,我在这里错过了一些东西。

编辑 1: 在斯特里比泽夫评论@Wiktor。 他指出(在他的链接之后):

import re
print(re.sub(r'(.)(.)(.)(.)(.)(.)', 'a6b', '123456'))
print(re.sub(r'(.)(.)(.)(.)(.)(.)', r'a6b', '123456'))
# in my example the substitutions were not the same and the result were equal
# here indeed r' changes the results

这给了:

ab
a6b

这让我更加困惑。

注意: 我读了这个关于原始字符串的堆栈溢出问题,这是超级完整的。然而,它没有谈到替代

首先,

replacement patterns ≠ regular expression patterns

我们使用正则表达式模式来搜索匹配项,我们使用替换模式来替换用正则表达式找到的匹配项。

注意替换模式中唯一的特殊字符是反斜杠。只有反斜杠必须加倍。

Python 中的替换模式语法

re.sub文档令人困惑,因为它们提到了可用于替换模式(如nr)和正则表达式转义序列(6)的字符串转义序列,以及可以用作正则表达式和字符串转义序列(&)的字符串转义序列。

我使用术语正则表达式转义序列来表示由文字反斜杠 + 字符(即'\X'r'X')组成的转义序列,以及字符串转义序列来表示序列和字符或某个序列共同形成有效的字符串转义序列。它们仅在常规字符串文本中识别。在原始字符串文字中,您只能转义"(这就是为什么您不能以"结束原始字符串文字的原因,但反冲仍然是字符串的一部分)。

因此,在替换模式中,您可以使用反向引用:

re.sub(r'D(d)D', r'1', 'a1b')    # => 1
re.sub(r'D(d)D', '\1', 'a1b')    # => 1
re.sub(r'D(d)D', 'g<1>', 'a1b')  # => 1
re.sub(r'D(d)D', r'g<1>', 'a1b') # => 1

您可能会看到r'1''\1'是相同的替换模式,1。如果使用'1',它将解析为字符串转义序列,一个具有八进制值的字符001。如果您忘记将前缀与明确的反向引用一起使用r则没有问题,因为g不是有效的字符串转义序列,并且转义字符保留在字符串中。阅读我链接到的文档:

与标准 C 不同,所有无法识别的转义序列都保留在字符串中不变,即反斜杠保留在结果中。

因此,当您将'.'作为替换字符串传递时,您实际上发送.两个字符的组合作为替换字符串,这就是您在结果中得到.的原因。

是 Python 替换模式中的特殊字符

如果您使用re.sub(r's+.', r'\.', text),您将获得与text2text3情况下相同的结果,请参阅此演示。

发生这种情况是因为\,两个文字反斜杠,表示替换模式中的单个反斜杠。如果您的正则表达式模式中没有组 2,但在替换中传递r'2'以实际替换为2字符组合,则会出现错误。

因此,当您拥有动态的、用户定义的替换模式时,您需要将替换模式中的所有反斜杠加倍,这些反斜杠将作为文本字符串传递:

re.sub(some_regex, some_replacement.replace('\', '\\'), input_string)

解决所有这些字符串转义问题的一种简单方法是使用 function/lambda 作为repl参数,而不是字符串。例如:

output = re.sub(
pattern=find_pattern,
repl=lambda _: replacement,
string=input,
)

替换字符串根本不会被解析,只是替换匹配项。

来自文档(我的强调):

re.sub(pattern, repl, string, count=0, flags=0) 返回字符串 通过替换最左边的非重叠出现项获得 替换 repl 的字符串模式。如果未找到模式, 字符串返回不变。repl 可以是字符串或函数;如果 它是一个字符串,其中的任何反斜杠转义都会被处理。也就是说, 转换为单个换行符,\r 转换为 回车等。ASCII 字母的未知转义是 保留供将来使用,并被视为错误。其他未知逃逸 比如\&被单独留下。反向引用(如 \6)被替换 子字符串与模式中的组 6 匹配。

repl参数不仅仅是纯文本。 它也可以是函数的名称或引用组中的位置(例如g<quote>g<1>1)。

另外,从这里:

与标准 C 不同,所有无法识别的转义序列都保留在 字符串不变,即反斜杠保留在结果中。

由于.不是特殊的转义字符,因此'.'r'.相同。

最新更新