13

我不明白 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 Stribiżew 评论。他指出(按照他的链接):

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

这使:

ab
a6b

这让我更加困惑。

注意:我阅读了这个关于原始字符串的堆栈溢出问题,它非常完整。尽管如此,它并没有谈到替代品

4

3 回答 3

7

首先也是最重要的,

replacement patterns ≠ regular expression patterns

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

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

Python中的替换模式语法

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

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

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

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'替换传递给实际替换为\2char 组合,则会出现错误。

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

re.sub(some_regex, some_replacement.replace('\\', '\\\\'), input_string)
于 2019-06-10T10:19:49.633 回答
2

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

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

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

于 2021-11-14T13:37:20.487 回答
1

来自文档(我的重点):

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

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

另外,从这里

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

由于.不是特殊的转义字符,'\.'因此与r'\.\.

于 2019-06-10T09:33:25.530 回答