在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'之间的区别,但是如果你在“给我一些不太有损失的东西,我可以稍后用手修复”的思想框架中,那可能没问题。
然而,这是Python3.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的旧版本不需要工作。猴子修补是完全可以接受的。)

最佳答案

我尝试对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'))

10-07 15:06