Python编码(encode)和解码(Decode)常见的两个错误
和分别介绍了 Python 中的字符串类型(str)和字节类型(byte),以及 Python 编码中最常见也是最顽固的两个错误:UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
UnicodeDecodeError: 'utf-8' codec can't decode bytes in position 0-1: invalid continuation byte
这⼀期就从这两个错误⼊⼿,分析 Python 中 Unicode 的正确⽤法。这篇短⽂并不能保证你可以永远杜绝上⾯两个错误,但是希望在下次遇到这个错误的时候知道错在哪⾥、应该从哪⾥⼊⼿。
编码与解码
上⾯的两个错误分别是UnicodeEncodeError和UnicodeDecodeError,也就是说分别在 Unicode 编码(Encode)和解码(Decode)过程中出现了错误,那么编码和解码究竟分别意味着什么?根据的定义:
字符编码(英语:Character encoding)、字集码是把字符集中的字符编码为指定集合中某⼀对象(例如:⽐特模式、⾃然数序列、8位组或者电脉冲),以便⽂本在计算机中存储和通过通信⽹络的传递。
简单来说就是把⼈类通⽤的语⾔符号翻译成计算机通⽤的对象,⽽反向的翻译过程⾃然就是解码了。Python 中的字符串类型代表⼈类通⽤的语⾔符号,因此字符串类型有encode()⽅法;⽽字节类型代表计算机通⽤的对象(⼆进制数据),因此字节类型有decode()⽅法。
print("  ".encode())
b'\xf0\x9f\x8c\x8e\xf0\x9f\x8c\x8f'
print(b'\xf0\x9f\x8c\x8e\xf0\x9f\x8c\x8f'.decode())
既然说编码和解码都是翻译的过程,那么就需要⼀本字典将⼈类和计算机的语⾔⼀⼀对应起来,这本字典的名字叫做字符集,从最早的ASCII 到现在最通⽤的 Unicode,它们的本质是⼀样的,只是两本字典的厚度不同⽽已。ASCII 只包含了26个基本拉丁字母、阿拉伯数⽬字和英式标点符号⼀共128个字符,因此只需要(不占满)⼀个字节就可以存储,⽽ Unicode 则涵盖的数据除了视觉上的字形、编码⽅法、标准的字符编码外,还包含了字符特性,如⼤⼩写字母,共可包含 1.1M 个字符,⽽到现在只填充了其中的 110K 个位置。
ascii共有多少个字符字符集中字符所存储的位置(或者说对应的计算机通⽤的数字)称之为码位(code point),例如在 ASCII 中字符'$'的码位就是:
print(ord('$'))
36
ASCII 只需要⼀个字节就能存下所有码位,⽽ Unicode 则需要⼏个字节才能容纳,但是对于具体采⽤什么样的⽅案来实现 Unicode 的这种映射关系,也有很多不同的⽅案(或规则),例如最常见(也是 Python 中默认的)UTF-8,还有 UTF-16、UTF-32 等,对于它们规则上的不同这⾥就不深⼊展开了。当然,在 ASCII 与 Unicode 之间还有很多其他的字符集与编码⽅案,例如中⽂编码的 GB2312、繁体字的 Big5等等,这并不影响我们对编码与解码过程的理解。
Unicode*Error
明⽩了字符串与字节,编码与解码之后,让我们⼿动制造上⾯两个Unicode*Error试试,⾸先是编码错误:
def tryEncode(s, encoding="utf-8"):
try:
de(encoding))
except UnicodeEncodeError as err:
print(err)
s = "$"          # UTF-8 String
tryEncode(s)          # 默认⽤ UTF-8 进⾏编码
tryEncode(s, "ascii") # 尝试⽤ ASCII 进⾏编码
s = "⾬"          # UTF-8 String
tryEncode(s)          # 默认⽤ UTF-8 进⾏编码
tryEncode(s, "ascii") # 尝试⽤ ASCII 进⾏编码
tryEncode(s, "GB2312")  # 尝试⽤ GB2312 进⾏编码
b'$'
b'$'
b'\xe9\x9b\xa8'
'ascii' codec can't encode character '\u96e8' in position 0: ordinal not in range(128)
b'\xd3\xea'
由于 UTF-8 对 ASCII 的兼容性,"$"可以⽤ ASCII 进⾏编码;⽽"⾬"则⽆法⽤ ASCII 进⾏编码,因为它已经超出了 ASCII 字符集的 128 个字符,所以引发了UnicodeEncodeError;⽽"⾬"在 GB2312 中的码位是b'\xd3\xea',与 UTF-8 不同,但是仍然可以正确编码。因此如果出现
了UnicodeEncodeError说明你⽤错了字典,要翻译的字符没办法正确翻译成码位!
再来看解码错误:
def tryDecode(s, decoding="utf-8"):
try:
print(s.decode(decoding))
except UnicodeDecodeError as err:
print(err)
b = b'$'    # Bytes
tryDecode(b)          # 默认⽤ UTF-8 进⾏解码
tryDecode(b, "ascii") # 尝试⽤ ASCII 进⾏解码
tryDecode(b, "GB2312") # 尝试⽤ GB2312 进⾏解码
b = b'\xd3\xea' # 上⾯例⼦中通过 GB2312 编码得到的 Bytes
tryDecode(b)          # 默认⽤ UTF-8 进⾏解码
tryDecode(b, "ascii")  # 尝试⽤ ASCII 进⾏解码
tryDecode(b, "GB2312") # 尝试⽤ GB2312 进⾏解码
tryDecode(b, "GBK")    # 尝试⽤ GBK 进⾏解码
tryDecode(b, "Big5")    # 尝试⽤ Big5 进⾏解码
tryDecode(b.decode("GB2312").encode()) # Byte-Decode-Unicode-Encode-Byte
$
$
$
'utf-8' codec can't decode byte 0xd3 in position 0: invalid continuation byte
'ascii' codec can't decode byte 0xd3 in position 0: ordinal not in range(128)
⼀般后续出现的字符集都是对 ASCII 兼容的,可以认为 ASCII 是他们的⼀个⼦集,因此可以⽤ ASCII
进⾏解码(编码)的,⼀般也可以⽤其它⽅法;对于不是不存在⼦集关系的编码,强⾏解码有可能会导致错误或乱码!
实践中的策略
清楚了上⾯介绍的所有原理之后,在时间操作中应该怎样规避错误或乱码呢?
1. 记清楚编码与解码的⽅向;
2. 在 Python 中的操作尽量采⽤ UTF-8,输⼊或输出的时候再根据需求确定是否需要编码成⼆进制:
#
# 你好,世界!
#
# : UTF-8 Unicode text
with open("", "rb") as f:
content = f.read()
print(content)
print(content.decode())
with open("", "r") as f:
ad())
#
# 你好,Unicode!
#
# : ISO-8859 text
with open("", "r") as f:
try:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。