【问题标题】:Process escape sequences in a string in Python在 Python 中处理字符串中的转义序列
【发布时间】:2024-01-15 01:14:01
【问题描述】:

有时当我从文件或用户获取输入时,我会得到一个包含转义序列的字符串。我想处理转义序列in the same way that Python processes escape sequences in string literals

例如,假设myString 定义为:

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

我想要一个执行此操作的函数(我称之为process):

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

重要的是,该函数可以处理 Python 中的所有转义序列(在上面链接的表格中列出)。

Python 有这样的功能吗?

【问题讨论】:

  • hmmm,您希望如何处理包含'spam'+"eggs"+'''some'''+"""more""" 的字符串?
  • @Nas Banov 这是一个很好的测试。该字符串不包含转义序列,因此处理后应该完全相同。 myString = "'spam'+\"eggs\"+'''some'''+\"\"\"more\"\"\""print(bytes(myString, "utf-8").decode("unicode_escape")) 似乎有效。
  • 这个问题的大多数答案都有严重的问题。在不破坏 unicode 的情况下,似乎没有标准的方法来尊重 Python 中的转义序列。 @rspeer 发布的答案是我为 Grako 采用的答案,因为它迄今为止处理了所有已知案例。
  • 我不同意阿帕拉拉;使用 unicode_escape (在正确的 latin1 编码的输入上)是完全可靠的,正如 Hack5 在他对 user19087 答案的评论中链接到的问题所示,是 python 开发人员推荐的方法。
  • 这能回答你的问题吗? How to un-escape a backslash-escaped string?

标签: python string escaping


【解决方案1】:

正确的做法是使用“字符串转义”代码来解码字符串。

>>> 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。使用字符串编解码器更安全。

【讨论】:

  • 毫无疑问,最佳解决方案!顺便说一句,根据文档,它应该是“string_escape”(带下划线),但由于某种原因,它接受“string escape”、“string@escape”等模式中的任何内容......基本上'string\W+escape'
  • @Nas Banov 文档确实有make a small mention about that: Notice that spelling alternatives that only differ in case or use a hyphen instead of an underscore are also valid aliases; therefore, e.g. 'utf-8' is a valid alias for the 'utf_8' codec.
  • 这个解决方案不够好,因为它不能处理原始字符串中有合法 unicode 字符的情况。如果你尝试:>>> print("juancarlo\\tañez".encode('utf-8').decode('unicode_escape')) 你会得到:juancarlo añez
  • 同意@Apalala:这还不够好。查看下面 rseeper 的答案,了解适用于 Python2 和 3 的完整解决方案!
  • 由于latin1unicode_escape 假定,重做编码/解码位,例如s.encode('utf-8').decode('unicode_escape').encode('latin1').decode('utf8')
【解决方案2】:

unicode_escape 一般不工作

事实证明,string_escapeunicode_escape 解决方案通常不起作用——尤其是在存在实际 Unicode 的情况下它不起作用。

如果您可以确定 每个 非 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 编解码器尽管名称如此,但事实证明它假定所有非 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)

添加正则表达式解决问题

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

我们需要做的只是将unicode_escape解码器应用于我们确定是ASCII文本的东西。特别是,我们可以确保仅将其应用于有效的 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

【讨论】:

  • 我们需要更多类似的答案类型。谢谢。
  • 这对os.sep 有效吗?我正在尝试这样做:patt = '^' + self.prefix + os.sep ; name = sub(decode_escapes(patt), '', name),但它不起作用。分号代替新行。
  • @Pureferret 我不太确定你在问什么,但你可能不应该在反斜杠具有不同含义的字符串上运行它,例如 Windows 文件路径。 (这就是您的os.sep 吗?)如果您的 Windows 目录名称中有反斜杠转义序列,则情况几乎无法恢复。
  • 转义序列中没有转义符,但我收到“假转义字符串”错误
  • 这告诉我你用反斜杠结束了其他一些正则表达式:*.com/questions/4427174/…
【解决方案3】:

Jerub 的(当前)接受的答案对于 python2 是正确的,但对于 python3.这是因为根据官方python docs,unicode_escape 编解码器要求其源代码以 latin-1 而不是 utf-8 编码。因此,在 python3 中使用:

>>> myString="špåm\\nëðþ\\x73"
>>> print(myString)
špåm\nëðþ\x73
>>> decoded_string = myString.encode('latin-1','backslashreplace').decode('unicode_escape')
>>> print(decoded_string)
špåm
ëðþs

此方法还避免了 metatoaster 的 cmets 中的字符串和字节到 Jerub 的解决方案之间额外不必要的往返(但要感谢 metatoaster 以识别该解决方案中的错误)。

【讨论】:

  • 当我发布这个时,我没有意识到有一个重复的问题已经给出了这个确切的答案:*.com/a/57192592/5583443
【解决方案4】:

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 是 python3 的错误解决方案。这是因为unicode_escape 将转义字节解码,然后将字节解码为 un​​icode 字符串,但没有收到关于第二次操作使用哪个编解码器的信息。
  • @Jerub 是正确的:避免使用 AST 或 eval。
  • 我首先从this answer to "how do I .decode('string-escape') in Python3?" 发现了codecs.escape_decode。正如该答案所述,python 3 目前没有记录该函数。

【讨论】:

  • 这是真正的答案(:太糟糕了,它依赖于记录不充分的函数。
  • 这是您拥有的转义序列为\x UTF-8 字节转义的情况的答案。但因为它将字节解码为字节,所以它不会 - 也不能 - 解码任何非 ASCII Unicode 字符的转义,例如 \u 转义。
  • 仅供参考,此功能在技术上是不公开的。见bugs.python.org/issue30588
  • 此外,在 Hack5 提供的链接中,python 维护人员明确表示,escape_decode 可能会在未来的任何版本中被删除而不会发出警告,并且推荐使用“unicode_escape”编解码器来解决这个问题.
【解决方案5】:

ast.literal_eval 函数很接近,但它希望首先正确引用字符串。

当然,Python 对反斜杠转义的解释取决于字符串的引用方式("" vs r"" vs u"",三引号等),因此您可能希望将用户输入包装在合适的引号中并传递给literal_eval。用引号括起来也可以防止 literal_eval 返回数字、元组、字典等。

如果用户键入您打算环绕字符串的类型的不带引号的引号,事情仍然可能会变得棘手。

【讨论】:

  • 我明白了。正如您所说,这似乎具有潜在危险:myString = "\"\ndoBadStuff()\n\""print(ast.literal_eval('"' + myString + '"')) 似乎试图运行代码。 ast.literal_evaleval 有何不同/更安全?
  • @dln385: literal_eval 从不执行代码。从文档中,“这可用于安全地评估包含来自不受信任来源的 Python 表达式的字符串,而无需自己解析值。”
【解决方案6】:

下面的代码应该适用于 \n 需要显示在字符串上。

import string

our_str = 'The String is \\n, \\n and \\n!'
new_str = string.replace(our_str, '/\\n', '/\n', 1)
print(new_str)

【讨论】:

  • 这不能像写的那样工作(正斜杠使 replace 什么都不做),使用过时的 API(这种类型的 string 模块函数从 Python 2.0 开始被弃用,被替换通过str 方法,并在 Python 3 中完全消失),并且只处理替换单个换行符的特定情况,而不是一般的转义处理。
【解决方案7】:

这是一种不好的做法,但在尝试解释在字符串参数中传递的转义八进制时它对我有用。

input_string = eval('b"' + sys.argv[1] + '"')

值得一提的是 eval 和 ast.literal_eval 之间存在差异(eval 更不安全)。见Using python's eval() vs. ast.literal_eval()?

【讨论】:

    最近更新 更多