关于python:如何在Python3中.decode(‘string-escape’)?

how do I .decode('string-escape') in Python3?

我有一些需要转义的转义字符串。 我想用Python做到这一点。

例如,在python2.7中,我可以这样做:

1
2
3
>>>"\\123omething special".decode('string-escape')
'Something special'
>>>

如何在Python3中做到这一点? 这不起作用:

1
2
3
4
5
>>> b"\\123omething special".decode('string-escape')
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
LookupError: unknown encoding: string-escape
>>>

我的目标是成为一个像这样的字符串:

1
s\000u\000p\000p\000o\000r\000t\000@\000p\000s\000i\000l\000o\000c\000.\000c\000o\000m\000

并将其转换为:

进行转换后,我将探查我拥有的字符串是否以UTF-8或UTF-16编码。


您必须改为使用unicode_escape

1
>>> b"\\123omething special".decode('unicode_escape')

如果从str对象开始(相当于python 2.7 unicode),则需要先编码为字节,然后再使用unicode_escape进行解码。

如果需要字节作为最终结果,则必须再次编码为合适的编码(例如,如果需要保留文字字节值,则为.encode('latin1');前256个Unicode代码点一对一映射)。

您的示例实际上是带有转义符的UTF-16数据。从unicode_escape解码,回到latin1以保留字节,然后从utf-16-le(不带BOM的UTF 16小端)进行解码:

1
2
3
4
5
>>> value = b's\\000u\\000p\\000p\\000o\\000r\\000t\\000@\\000p\\000s\\000i\\000l\\000o\\000c\\000.\\000c\\000o\\000m\\000'
>>> value.decode('unicode_escape').encode('latin1')  # convert to bytes
b's\x00u\x00p\x00p\x00o\x00r\x00t\x00@\x00p\x00s\x00i\x00l\x00o\x00c\x00.\x00c\x00o\x00m\x00'
>>> _.decode('utf-16-le') # decode from UTF-16-LE
'[email protected]'


旧的"字符串转义"编解码器将字节字符串映射为字节字符串,并且关于如何使用此类编解码器存在很多争论,因此当前无法通过标准的编码/解码接口使用。

但是,代码仍在C-API中(如PyBytes_En/DecodeEscape),并且仍通过未记录的codecs.escape_encodecodecs.escape_decode暴露给Python。

1
2
3
4
5
>>> import codecs
>>> codecs.escape_decode(b"ab\\xff")
(b'ab\xff', 6)
>>> codecs.escape_encode(b"ab\xff")
(b'ab\\xff', 3)

这些函数返回转换后的bytes对象,再加上一个数字,指示要处理的字节数……您可以忽略后者。

1
2
3
>>> value = b's\\000u\\000p\\000p\\000o\\000r\\000t\\000@\\000p\\000s\\000i\\000l\\000o\\000c\\000.\\000c\\000o\\000m\\000'
>>> codecs.escape_decode(value)[0]
b's\x00u\x00p\x00p\x00o\x00r\x00t\x00@\x00p\x00s\x00i\x00l\x00o\x00c\x00.\x00c\x00o\x00m\x00'


您不能在字节字符串上使用unicode_escape(或者可以,但它并不总是与Python 2上的string_escape返回相同的内容)–注意!

此函数使用正则表达式和自定义替换逻辑实现string_escape

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def unescape(text):
    regex = re.compile(b'\\\\(\\\\|[0-7]{1,3}|x.[0-9a-f]?|[\'"abfnrt]|.|$)')
    def replace(m):
        b = m.group(1)
        if len(b) == 0:
            raise ValueError("Invalid character escape: '\'.")
        i = b[0]
        if i == 120:
            v = int(b[1:], 16)
        elif 48 <= i <= 55:
            v = int(b, 8)
        elif i == 34: return b'"'
        elif i == 39: return b"'"
        elif i == 92: return b'\'
        elif i == 97: return b'
\a'
        elif i == 98: return b'
\b'
        elif i == 102: return b'
\f'
        elif i == 110: return b'

'
        elif i == 114: return b'

'
        elif i == 116: return b'
\t'
        else:
            s = b.decode('
ascii')
            raise UnicodeDecodeError(
                '
stringescape', text, m.start(), m.end(),"Invalid escape: %r" % s
            )
        return bytes((v, ))
    result = regex.sub(replace, text)

至少在我看来,这是等效的:

1
2
Py2: my_input.decode('string_escape')
Py3: bytes(my_input.decode('unicode_escape'), 'latin1')

convertutils.py:

1
2
def string_escape(my_bytes):
    return bytes(my_bytes.decode('unicode_escape'), 'latin1')