转义正则表达式字符串

352

我想使用用户输入作为正则表达式模式来搜索一些文本。虽然它可以工作,但我该如何处理用户输入中包含正则表达式意义字符的情况?

例如,用户想要搜索单词(s):正则引擎将把(s)视为一个组。我希望它将其视为一个字符串"(s)"。我可以在用户输入上运行replace并将(替换为\(,将)替换为\),但问题是我需要为每个可能的正则表达式符号进行替换。

你知道更好的方法吗?


在正则表达式和匹配模式/捕获组到大字符串的情境下,这通常用于什么目的? - Charlie Parker
我认为我的回答很好地解释了原则:https://dev59.com/L3VC5IYBdhLWcg3weBE-#73068412 - Charlie Parker
这是一个非常重要的问题,并有很多有效的用例,但是重要的是不要在不必要的情况下使用正则表达式。如果目标只是检查text是否包含某些其他文字面值的user_input字符串,则可以内置此功能,没有理由使用正则表达式-只需检查user_input in text即可。请参阅Python是否有包含子字符串的方法? - Karl Knechtel
4个回答

455

使用re.escape()函数来实现:

4.2.3 re 模块内容

escape(string)

返回将所有非字母数字字符进行反斜杠转义后的字符串;如果您想要匹配一个包含正则表达式元字符的任意文本字符串,这将很有用。

下面是一个简单的示例,搜索任何提供的字符串,并可选地跟随 's',然后返回匹配对象。

def simplistic_plural(word, text):
    word_or_plural = re.escape(word) + 's?'
    return re.match(word_or_plural, text)

3
我不明白为什么这篇文章有这么多赞。它没有解释我们何时或为什么要使用转义,甚至没有提到原始字符串的相关性,而我认为这很重要,可以帮助我们理解何时使用它。 - Charlie Parker
@CharlieParker 很多 Python 的规范都很混乱。我发现与字符串转义、字符串表示(“如果我不使用 print,为什么在 REPL 输出中会得到这些内容?如果我使用 print,为什么会得到其他内容?”)以及正则表达式相关的主题尤其糟糕。它需要自上而下的规划和设计,这并不来自于有机的 Stack Overflow 问题提问过程。 - Karl Knechtel

83

你可以使用re.escape():

re.escape(string)返回具有所有非字母数字字符转义的字符串;如果您想匹配可能包含正则表达式元字符的任意文本字符串,则此方法非常有用。

>>> import re
>>> re.escape('^a.*$')
'\\^a\\.\\*\\$'

如果您使用的是Python版本<3.7,则它将转义非字母数字字符,这些字符属于正则表达式语法。

如果您使用的是Python版本<3.7但>= 3.3,则它将转义非字母数字字符,这些字符属于正则表达式语法,除了具体的下划线(_)。


传递原始字符串不够吗?还是你想匹配字面上的 ^?我通常使用 re.escape 强制匹配我想要字面匹配的内容,比如括号和空格。 - Charlie Parker
@CharlieParker,这个问题的假设在于我们必须能够匹配字面上的 ^ - Karl Knechtel

12

很遗憾,re.escape() 不适合于替换字符串:

>>> re.sub('a', re.escape('_'), 'aa')
'\\_\\_'

一种解决方案是将替换内容放在 lambda 中:

>>> re.sub('a', lambda _: '_', 'aa')
'__'

因为lambda的返回值被re.sub()视为一个字面字符串。


8
re.sub 函数的 repl 参数是一个字符串,而不是正则表达式;因此对其应用 re.escape 没有任何意义。 - tripleee
12
@tripleee 这是不正确的,repl参数不是一个简单的字符串,它被解析。例如,re.sub(r'(.)', r'\1', 'X')将返回X,而不是\1 - Flimm
10
以下是适用于避开repl参数的相关问题链接:https://dev59.com/2qrka4cB1Zd3GeqPi8fA - Flimm
9
自3.3版本起,“_”字符不再被转义。自3.7版本起,只有可能在正则表达式中具有特殊含义的字符才会被转义。 (为什么要这么长时间才做出更改?) - Cees Timmerman
@Flimm 不是第二个参数是正则表达式,而是\\1返回了X。你可以使用re.sub(r'(.)', '\\1', 'X')得到完全相同的结果。据我所知,没有理由在第二个参数中使用正则表达式。我只能猜测它对你起作用是因为它使用了正则表达式的字符串表示形式,即str(r"\1") == "\\1" - Seth Falco

-2
通常,对输入到正则表达式中的字符串进行转义,使得正则表达式将这些字符视为字面量。通常情况下,您在计算机上键入字符串,计算机会插入特定的字符。当您在编辑器中看到\n时,直到解析器决定它是新行之前,它并不是真正的新行。它是两个字符。一旦您通过Python的print传递它,它将显示并解析为新行,但在编辑器中看到的文本很可能只是反斜杠后跟n的字符。如果您执行\r"\n",那么Python将始终将其解释为您键入的原始内容(据我所知)。更进一步复杂化的是,正则表达式还有另一种语法/语法规则。正则表达式解析器将以不同于Python的打印方式接收到的字符串进行解释。我相信这就是为什么我们建议传递原始字符串,例如r"(\n+),以便正则表达式接收到您实际键入的内容。然而,正则表达式将接收到一个括号,并且除非您明确告诉它使用正则表达式自己的语法规则,否则它将不会将其匹配为字面括号。为此,您需要r"(\fun \( x : nat \) :)",这里第一个括号不会匹配,因为它是捕获组,由于缺少反斜杠,但第二个括号将被匹配为字面括号。
通常我们会使用re.escape(regex)来转义那些我们希望被字面解释的内容,即那些通常会被正则表达式解析器忽略的内容,例如括号、空格等都将被转义。例如,我在我的应用程序中使用的代码:
    # escapes non-alphanumeric to help match arbitrary literal string, I think the reason this is here is to help differentiate the things escaped from the regex we are inserting in the next line and the literal things we wanted escaped.
    __ppt = re.escape(_ppt)  # used for e.g. parenthesis ( are not interpreted as was to group this but literally

例如,看看这些字符串:

_ppt
Out[4]: '(let H : forall x : bool, negb (negb x) = x := fun x : bool =>HEREinHERE)'
__ppt
Out[5]: '\\(let\\ H\\ :\\ forall\\ x\\ :\\ bool,\\ negb\\ \\(negb\\ x\\)\\ =\\ x\\ :=\\ fun\\ x\\ :\\ bool\\ =>HEREinHERE\\)'
print(rf'{_ppt=}')
_ppt='(let H : forall x : bool, negb (negb x) = x := fun x : bool =>HEREinHERE)'
print(rf'{__ppt=}')
__ppt='\\(let\\ H\\ :\\ forall\\ x\\ :\\ bool,\\ negb\\ \\(negb\\ x\\)\\ =\\ x\\ :=\\ fun\\ x\\ :\\ bool\\ =>HEREinHERE\\)'

我相信双反斜杠是为了让正则表达式接收到一个字面上的反斜杠。


顺便说一下,我很惊讶它打印了双反斜杠而不是单个。如果有人能够对此发表评论,将不胜感激。我也很好奇如何在正则表达式中匹配字面上的反斜杠。我假设需要4个反斜杠,但实际上由于原始字符串r结构,我认为只需要2个反斜杠。


顺便说一句,我很惊讶它打印了两个反斜杠而不是一个。如果有人能对此发表评论,那将不胜感激。我也很好奇如何在正则表达式中匹配字面上的反斜杠。我假设需要4个反斜杠,但由于原始字符串r的构造,我实际上只需要2个反斜杠。 - Charlie Parker
1
请阅读[答案],注意这不是一个讨论论坛。 - Karl Knechtel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接