在Python 2中确切等同于'b'...'.decode(“utf-8”,“backslashreplace”)`

问题描述 投票:13回答:2

在Python 3.5+中,.decode("utf-8", "backslashreplace")是处理部分Unicode,部分未知遗留编码二进制字符串的相当不错的选择。将解码有效的UTF-8序列,并将无效的序列保留为转义序列。例如

>>> print(b'\xc2\xa1\xa1'.decode("utf-8", "backslashreplace"))
¡\xa1

这失去了b'\xc2\xa1\xa1'b'\xc2\xa1\\xa1'之间的区别,但如果你在“只是给我一些不太有损的东西,以后我可以手工修理”的心态,那可能就行了。

但是,这是Python 3.5中的一项新功能。我正在研究的程序也需要支持3.4和2.7。在这些版本中,它会抛出异常:

>>> print(b'\xc2\xa1\xa1'.decode("utf-8", "backslashreplace"))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/encodings/utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
TypeError: don't know how to handle UnicodeDecodeError in error callback

我找到了一个近似值,但不是一个确切的等价物:

>>> print(b'\xc2\xa1\xa1'.decode("latin1")
...       .encode("ascii", "backslashreplace").decode("ascii"))
\xc2\xa1\xa1

行为不依赖于解释器版本是非常重要的。任何人都可以建议一种方法来获得2.7和3.4中的Python 3.5行为吗?

(较旧版本的2.x或3.x不需要工作。猴子修补codecs是完全可以接受的。)

python python-2.7 python-3.x encoding backwards-compatibility
2个回答
7
投票

我尝试了更完整的cpython implementation后退

它处理UnicodeDecodeError(来自.decode())以及来自UnicodeEncodeError.encode()和来自UnicodeTranslateError.translate()

from __future__ import unicode_literals

import codecs


def _bytes_repr(c):
    """py2: bytes, py3: int"""
    if not isinstance(c, int):
        c = ord(c)
    return '\\x{:x}'.format(c)


def _text_repr(c):
    d = ord(c)
    if d >= 0x10000:
        return '\\U{:08x}'.format(d)
    else:
        return '\\u{:04x}'.format(d)


def backslashescape_backport(ex):
    s, start, end = ex.object, ex.start, ex.end
    c_repr = _bytes_repr if isinstance(ex, UnicodeDecodeError) else _text_repr
    return ''.join(c_repr(c) for c in s[start:end]), end


codecs.register_error('backslashescape_backport', backslashescape_backport)

print(b'\xc2\xa1\xa1after'.decode('utf-8', 'backslashescape_backport'))
print(u'\u2603'.encode('latin1', 'backslashescape_backport'))

4
投票

您可以编写自己的错误处理程序。这是我在Python 2.7,3.3和3.6上测试的解决方案:

from __future__ import print_function
import codecs
import sys

print(sys.version)

def myreplace(ex):
    # The error handler receives the UnicodeDecodeError, which contains arguments of the
    # string and start/end indexes of the bad portion.
    bstr,start,end = ex.object,ex.start,ex.end

    # The return value is a tuple of Unicode string and the index to continue conversion.
    # Note: iterating byte strings returns int on 3.x but str on 2.x
    return u''.join('\\x{:02x}'.format(c if isinstance(c,int) else ord(c))
                    for c in bstr[start:end]),end

codecs.register_error('myreplace',myreplace)
print(b'\xc2\xa1\xa1ABC'.decode("utf-8", "myreplace"))

输出:

C:\>py -2.7 test.py
2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 20:42:59) [MSC v.1500 32 bit (Intel)]
¡\xa1ABC

C:\>py -3.3 test.py
3.3.5 (v3.3.5:62cf4e77f785, Mar  9 2014, 10:35:05) [MSC v.1600 64 bit (AMD64)]
¡\xa1ABC

C:\>py -3.6 test.py
3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)]
¡\xa1ABC
© www.soinside.com 2019 - 2024. All rights reserved.