项目地址:https://git.io/pytipshtml
0x07 和 0x08 分别介绍了 Python 中的字符串类型(str
)和字节类型(byte
),以及 Python 编码中最多见也是最顽固的两个错误:python
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)git
UnicodeDecodeError: 'utf-8' codec can't decode bytes in position 0-1: invalid continuation bytegithub
这一期就从这两个错误入手,分析 Python 中 Unicode 的正确用法。这篇短文并不能保证你能够永远杜绝上面两个错误,可是但愿在下次遇到这个错误的时候知道错在哪里、应该从哪里入手。网络
上面的两个错误分别是 UnicodeEncodeError
和 UnicodeDecodeError
,也就是说分别在 Unicode 编码(Encode)和解码(Decode)过程当中出现了错误,那么编码和解码究竟分别意味着什么?根据维基百科字符编码的定义:编码
字符编码(英语:Character encoding)、字集码是把字符集中的字符编码为指定集合中某一对象(例如:比特模式、天然数序列、8位组或者电脉冲),以便文本在计算机中存储和经过通讯网络的传递。spa
简单来讲就是把人类通用的语言符号翻译成计算机通用的对象,而反向的翻译过程天然就是解码了。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 个位置。code
字符集中字符所存储的位置(或者说对应的计算机通用的数字)称之为码位(code point),例如在 ASCII 中字符 '$'
的码位就是:htm
print(ord('$'))
36
ASCII 只须要一个字节就能存下全部码位,而 Unicode 则须要几个字节才能容纳,可是对于具体采用什么样的方案来实现 Unicode 的这种映射关系,也有不少不一样的方案(或规则),例如最多见(也是 Python 中默认的)UTF-8,还有 UTF-1六、UTF-32 等,对于它们规则上的不一样这里就不深刻展开了。固然,在 ASCII 与 Unicode 之间还有不少其余的字符集与编码方案,例如中文编码的 GB23十二、繁体字的 Big5 等等,这并不影响咱们对编码与解码过程的理解。
明白了字符串与字节,编码与解码以后,让咱们手动制造上面两个 Unicode*Error
试试,首先是编码错误:
def tryEncode(s, encoding="utf-8"): try: print(s.encode(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 进行解码(编码)的,通常也能够用其它方法;对于不是不存在子集关系的编码,强行解码有可能会致使错误或乱码!
清楚了上面介绍的全部原理以后,在时间操做中应该怎样规避错误或乱码呢?
记清楚编码与解码的方向;
在 Python 中的操做尽可能采用 UTF-8,输入或输出的时候再根据需求肯定是否须要编码成二进制:
# cat utf8.txt # 你好,世界! # file utf8.txt # utf8.txt: UTF-8 Unicode text with open("utf8.txt", "rb") as f: content = f.read() print(content) print(content.decode()) with open("utf8.txt", "r") as f: print(f.read()) # cat gb2312.txt # 你好,Unicode! # file gb2312.txt # gb2312.txt: ISO-8859 text with open("gb2312.txt", "r") as f: try: print(f.read()) except: print("Failed to decode file!") with open("gb2312.txt", "rb") as f: print(f.read().decode("gb2312"))
b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c\xef\xbc\x81\n' 你好,世界! 你好,世界! Failed to decode file! 你好,Unicode!
欢迎关注 PyHub!