Python 正则表达式转义运算符 \ 在替换和原始字符串中
Posted
技术标签:
【中文标题】Python 正则表达式转义运算符 \\ 在替换和原始字符串中【英文标题】:Python Regex escape operator \ in substitutions & raw stringsPython 正则表达式转义运算符 \ 在替换和原始字符串中 【发布时间】:2019-10-24 16:27:16 【问题描述】:我不明白 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
这让我更加困惑。
注意: 我阅读了关于原始字符串的this 堆栈溢出问题,该问题非常完整。尽管如此,它并没有谈到替换
【问题讨论】:
它没有“谈论”替换,因为替换模式不是正则表达式。'\.'
= r'\.'
,它是 \
和 .
字符组合。由于它是一个替换模式,因此您可以在结果中得到该文本。但是,您在测试中使用了\
,它甚至更加棘手:它在正则表达式替换模式中是 special 的。 re.sub(r'\s+\.', r'\\.', text)
将产生与text2
和text3
相同的字符串。见this Python demo。
【参考方案1】:
来自doc(我的重点):
re.sub(模式,repl,字符串,count=0,flags=0) 返回字符串 通过替换最左边的非重叠出现 替换repl在字符串中的模式。如果没有找到该模式, 字符串原样返回。 repl 可以是字符串或函数;如果 它是一个字符串,其中的任何反斜杠转义都会被处理。也就是说,\n 转换为单个换行符,\r 转换为 回车,等等。 ASCII 字母的未知转义是 保留供将来使用并视为错误。 其他未知的逃脱 如 \& 被单独保留。 反向引用,如 \6,被替换 与模式中第 6 组匹配的子字符串。
repl 参数不仅仅是纯文本。它也可以是函数的名称或引用组中的位置(例如\g<quote>
、\g<1>
、\1
)。
另外,来自here:
与标准 C 不同,所有无法识别的转义序列都留在 字符串不变,即反斜杠留在结果中。
由于.
不是特殊的转义字符,'\.'
与r'\.\
相同。
【讨论】:
【参考方案2】:首先,
replacement patterns ≠ regular expression patterns
我们使用 regex 模式 来搜索匹配项,我们使用 replacement patterns 来替换使用 regex 找到的匹配项。
注意:替换模式中唯一的特殊字符是反斜杠,\
。只有反斜杠必须加倍。
Python 中的替换模式语法
re.sub
docs 令人困惑,因为它们提到了可以在替换模式中使用的字符串转义序列(如\n
、\r
)和正则表达式转义序列(\6
)以及可以同时用作两者的转义序列正则表达式和字符串转义序列 (\&
)。
我使用术语正则表达式转义序列来表示由文字反斜杠+一个字符组成的转义序列,即'\\X'
或r'\X'
,以及一个字符串转义序列 来表示\
的序列和一个字符或一些序列,它们一起形成一个有效的string escape sequence。它们仅在 regular string literals 中被识别。在原始字符串文字中,您只能转义 "
(这就是为什么您不能以 \"
结束原始字符串文字的原因,但反冲仍然是字符串的一部分)。
因此,在替换模式中,您可以使用反向引用:
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)
,您将获得与text2
和text3
相同的结果,请参阅this demo。
这是因为\\
,两个文字反斜杠,表示替换模式中的一个反斜杠。如果您的正则表达式模式中没有第 2 组,但在替换中传递 r'\2'
以实际替换为 \
和 2
字符组合,则会出现错误。
因此,当您有动态的、用户定义的替换模式时,您需要将替换模式中的所有反斜杠加倍,这些反斜杠旨在作为文字字符串传递:
re.sub(some_regex, some_replacement.replace('\\', '\\\\'), input_string)
【讨论】:
感谢您的回答,解决了this question!旁注:有趣的是我们必须使用replace(...)
来替换re.sub
。递归!
@Basj 实际上,通常的做法是对动态替换模式进行预处理,结果应该保持文字不变。在 Java 中,有专门为此目的设计的 Matcher.quoteReplacement
方法。但是,替换模式中的特殊字符集因语言而异。
这表明 Python 应该有 re.escape_repl
或 re.escape(..., repl=True)
或 re.escape(..., mode='repl')
来转义 replacement 模式,除了 re.escape
转义正则表达式 search 模式。你觉得@WiktorStribiżew 怎么样?
@Basj 是的,这是对re
API 的一个很好的补充。【参考方案3】:
解决所有这些字符串转义问题的简单方法是使用函数/lambda 作为repl
参数,而不是字符串。例如:
output = re.sub(
pattern=find_pattern,
repl=lambda _: replacement,
string=input,
)
替换字符串根本不会被解析,只是替换匹配的位置。
【讨论】:
以上是关于Python 正则表达式转义运算符 \ 在替换和原始字符串中的主要内容,如果未能解决你的问题,请参考以下文章