在Python中处理字符串中的转义序列

173 投票
8 回答
149404 浏览
提问于 2025-04-16 06:05

有时候,当我从文件或者用户那里获取输入时,得到的字符串里面会包含一些转义序列。我想要以和Python处理字符串中的转义序列一样的方式来处理这些转义序列。

比如说,假设myString是这样定义的:

>>> myString = "spam\\neggs"
>>> print(myString)
spam\neggs

我想要一个函数(我叫它process),它能做到这一点:

>>> print(process(myString))
spam
eggs

这个函数很重要,因为它需要能够处理Python中所有的转义序列(在上面的链接中有一个表格列出了这些转义序列)。

Python有没有这样的函数呢?

8 个回答

46

对于Python 3,正确且方便的答案是:

>>> import codecs
>>> myString = "spam\\neggs"
>>> print(codecs.escape_decode(bytes(myString, "utf-8"))[0].decode("utf-8"))
spam
eggs
>>> myString = "naïve \\t test"
>>> print(codecs.escape_decode(bytes(myString, "utf-8"))[0].decode("utf-8"))
naïve    test

关于 codecs.escape_decode 的一些细节:

  • codecs.escape_decode 是一种字节到字节的解码器。
  • codecs.escape_decode 可以解码ASCII转义序列,比如:b"\\n" 会变成 b"\n"b"\\xce" 会变成 b"\xce"
  • codecs.escape_decode 不需要知道字节对象的编码方式,但被转义的字节的编码应该和对象的其他部分的编码一致。

背景信息:

  • @rspeer 说得对:unicode_escape 在Python 3中是错误的解决方案。因为 unicode_escape 先解码被转义的字节,然后再把字节解码成Unicode字符串,但它不知道第二步该用哪个编码。
  • @Jerub 也说得对:应该避免使用AST或eval。
  • 我第一次发现 codecs.escape_decode 是通过这个回答,关于“如何在Python 3中使用 .decode('string-escape')?”。正如那个回答所说,这个函数在Python 3中目前没有文档说明。
170

unicode_escape 并不总是有效

事实证明,string_escapeunicode_escape 的方法并不总是有效,特别是在有实际的Unicode字符存在时,它们就会出问题。

如果你能确保每一个非ASCII字符都会被转义(记住,ASCII字符是前128个字符,其他的都是非ASCII),那么 unicode_escape 就能正常工作。但是如果你的字符串中已经有了非ASCII字符,那就会出错。

unicode_escape 的基本设计是将字节转换为Unicode文本。但在很多地方,比如Python的源代码,源数据已经是Unicode文本了。

要让这个方法正确工作,唯一的办法是先把文本编码成字节。UTF-8是处理所有文本的合理编码,所以这样做应该没问题,对吧?

以下示例使用的是Python 3,这样字符串字面量看起来更简洁,但在Python 2和3中也会出现类似的问题,只是表现形式稍有不同。

>>> s = 'naïve \\t test'
>>> print(s.encode('utf-8').decode('unicode_escape'))
naïve   test

好吧,这个想法是错的。

现在推荐的方式是直接调用 codecs.decode 来解码文本。这样做有帮助吗?

>>> import codecs
>>> print(codecs.decode(s, 'unicode_escape'))
naïve   test

一点帮助都没有。(另外,上面的代码在Python 2中会引发UnicodeError。)

尽管 unicode_escape 的名字里有“unicode”,但它实际上假设所有非ASCII字节都是用Latin-1(ISO-8859-1)编码的。所以你必须这样做:

>>> print(s.encode('latin-1').decode('unicode_escape'))
naïve    test

但这太糟糕了。这限制了你只能使用256个Latin-1字符,仿佛Unicode根本没有被发明过一样!

>>> print('Ernő \\t Rubik'.encode('latin-1').decode('unicode_escape'))
UnicodeEncodeError: 'latin-1' codec can't encode character '\u0151'
in position 3: ordinal not in range(256)

添加正则表达式来解决问题

(令人惊讶的是,我们现在有了两个问题。)

我们需要做的是只对我们确定是ASCII文本的内容应用 unicode_escape 解码器。特别是,我们可以确保只对有效的Python转义序列应用它,这些序列保证是ASCII文本。

我们的计划是,使用正则表达式找到转义序列,并用一个函数作为 re.sub 的参数,将它们替换为未转义的值。

import re
import codecs

ESCAPE_SEQUENCE_RE = re.compile(r'''
    ( \\U........      # 8-digit hex escapes
    | \\u....          # 4-digit hex escapes
    | \\x..            # 2-digit hex escapes
    | \\[0-7]{1,3}     # Octal escapes
    | \\N\{[^}]+\}     # Unicode characters by name
    | \\[\\'"abfnrtv]  # Single-character escapes
    )''', re.UNICODE | re.VERBOSE)

def decode_escapes(s):
    def decode_match(match):
        return codecs.decode(match.group(0), 'unicode-escape')

    return ESCAPE_SEQUENCE_RE.sub(decode_match, s)

这样一来:

>>> print(decode_escapes('Ernő \\t Rubik'))
Ernő     Rubik
184

正确的做法是使用 'string-escape' 代码来解码字符串。

>>> myString = "spam\\neggs"
>>> decoded_string = bytes(myString, "utf-8").decode("unicode_escape") # python3 
>>> decoded_string = myString.decode('string_escape') # python2
>>> print(decoded_string)
spam
eggs

不要使用 AST 或 eval。这些字符串编码的方法要安全得多。

撰写回答