Python re.sub:在替换字符串中忽略反向引用

8 投票
2 回答
3315 浏览
提问于 2025-04-17 08:01

我想用一个字符串替换掉某个模式。这个字符串是存储在一个变量里的。这个字符串可能会包含‘\1’,但我希望它不要被当作反向引用来解释,而是简单地当作‘\1’来看待。

我该怎么做呢?

2 个回答

5

由于一些评论,我思考了很久这个问题并进行了尝试。这让我对转义字符有了更深的理解,所以我几乎完全修改了我的回答,希望对后来的读者有帮助。

NullUserException给出了简短的版本,我会尝试详细解释一下。感谢Qtax和Duncan的批评性反馈,希望这个回答现在是正确且有用的。

反斜杠(\)有特殊的含义,它是字符串中的转义字符。这意味着反斜杠和后面的字符组合在一起形成一个转义序列,当对字符串进行某些操作时,这个序列会被转换成其他东西。这个“某些操作”实际上就是创建字符串。因此,如果你想字面上使用\,你需要对它进行转义。这个转义字符就是反斜杠本身。

为了更好地理解发生了什么,我先给出一些例子。我还会打印出字符串中字符的ASCII码,希望能帮助理解发生了什么。

s = "A\1\nB"
print s
print [x for x in s]
print [hex(ord(x)) for x in s]

正在打印

A
B
['A', '\x01', '\n', 'B']
['0x41', '0x1', '0xa', '0x42']

所以当我在代码中输入\1时,s并不包含这两个字符,而是包含了ASCII字符0x01,它表示“开始标题”。\n也是一样,它被转换成0x0a,也就是换行符。

由于这种行为并不总是想要的,可以使用原始字符串,这样转义序列就会被忽略。

s = r"A\1\nB"
print s
print [x for x in s]
print [hex(ord(x)) for x in s]

我只是在字符串前面加了r,结果现在是

A\1\nB
['A', '\\', '1', '\\', 'n', 'B']
['0x41', '0x5c', '0x31', '0x5c', '0x6e', '0x42']

所有字符都按我输入的那样打印出来。

这就是我们所处的情况。接下来还有另一件事。

可能会有这样的情况:一个字符串应该被传递给正则表达式,要求字面上找到它,因此每个在正则表达式中有特殊含义的字符(例如 +*$[.) 都需要转义,因此有一个特殊的函数re.escape来完成这个工作。

但对于这个问题来说,这个函数是不对的,因为字符串不应该在正则表达式中使用,而是作为re.sub的替换字符串。

所以新的情况是:

一个包含转义序列的原始字符串应该用作re.sub的替换字符串。re.sub也会处理转义序列,但与之前的处理有一个小但重要的区别:\n仍然会被转换成0x0a,也就是换行符,但\1的处理方式现在改变了!它将被替换为re.sub中正则表达式捕获组1的内容。

s = r"A\1\nB"
print re.sub(r"(Replace)" ,s , "1 Replace 2")

结果是

1 AReplace
B 2

这个\1被替换为捕获组的内容,而\n被替换为换行符。

重要的是,你必须理解这种行为,现在在我看来你有两种选择(我不打算判断哪种是正确的):

  1. 创建者对字符串的行为不确定,如果他输入\n,那么他想要一个换行。在这种情况下,只需转义后面跟着数字的\

    OnlyDigits = re.sub(r"(Replace)" ,re.sub(r"(\\)(?=\d)", r"\\\\", s) , "1 Replace 2")
    print OnlyDigits
    print [x for x in OnlyDigits]
    print [hex(ord(x)) for x in OnlyDigits
    

    输出:

    1 A\1
    B 2
    ['1', ' ', 'A', '\\', '1', '\n', 'B', ' ', '2']
    ['0x31', '0x20', '0x41', '0x5c', '0x31', '0xa', '0x42', '0x20', '0x32']
    
  2. 创建者知道自己在做什么,如果他想要换行,他会输入\0xa。在这种情况下,转义所有的

    All = re.sub(r"(Replace)" ,re.sub(r"(\\)", r"\\\\", s) , "1 Replace 2")
    print All
    print [x for x in All]
    print [hex(ord(x)) for x in All]
    

    输出:

    1 A\1\nB 2
    ['1', ' ', 'A', '\\', '1', '\\', 'n', 'B', ' ', '2']
    ['0x31', '0x20', '0x41', '0x5c', '0x31', '0x5c', '0x6e', '0x42', '0x20', '0x32']
    
6

之前的回答提到使用 re.escape(),但这样会转义太多字符,导致替换后的字符串和被替换的字符串中出现不必要的反斜杠。

在Python中,似乎只需要在替换字符串中对反斜杠进行转义,所以像这样就足够了:

replacement = replacement.replace("\\", "\\\\")

示例

import re

x = r'hai! \1 <ops> $1 \' \x \\'
print "want to see: "
print x

print "getting: "
print re.sub(".(.).", x, "###")
print "over escaped: "
print re.sub(".(.).", re.escape(x), "###")
print "could work: "
print re.sub(".(.).", x.replace("\\", "\\\\"), "###")

输出:

want to see: 
hai! \1 <ops> $1 \' \x \\
getting: 
hai! # <ops> $1 \' \x \
over escaped: 
hai\!\ \1\ \<ops\>\ \$1\ \\'\ \x\ \\
could work: 
hai! \1 <ops> $1 \' \x \\

撰写回答