完全等同于 Python 中的 `b'...'.decode("utf-8", "backslashreplace")` 2
Exact equivalent of `b'...'.decode("utf-8", "backslashreplace")` in Python 2
In 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'
之间的区别,但如果您处于 "just get me something not too lossy that I can fix up by hand later" 的心态,那可能没问题。
但是,这是 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 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
的更完整的向后移植
这处理 UnicodeDecodeError
(来自 .decode()
)以及来自 .encode()
的 UnicodeEncodeError
和来自 .translate()
的 UnicodeTranslateError
:
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'))
In 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'
之间的区别,但如果您处于 "just get me something not too lossy that I can fix up by hand later" 的心态,那可能没问题。
但是,这是 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 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
这处理 UnicodeDecodeError
(来自 .decode()
)以及来自 .encode()
的 UnicodeEncodeError
和来自 .translate()
的 UnicodeTranslateError
:
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'))