在计算机里,一切都是用二进制存储的,比如 a 这个字母,在计算机里,用 0110 0001 这个8个bit来表示,8个bit就是一个字节。所谓ascii,就是一个字符编码,它规定了英文中的各种字符在计算机里表示形式。
ascii码作为一种字符编码,可以表示128个字符与二进制之间的关系,字符a的二进制编码是“0110 0001”,把这个二进制转成10进制就是97,下面的代码可以处理这种关系的转换
en_str = 'a'
en_ascii = ord(en_str)
print(en_ascii, type(en_ascii))
print(chr(97))
输出结果
97
a
2.1 大一统
只要稍微一思考,就会发现一个严重的问题,ascii码只是对英文的字符进行编码,可是这个世界上的语言文字又不仅仅只有英文,我们常用的汉字就有几千个,可ascii码只能对128个字符进行编码,这让我们中国人咋办
于是乎,我们中国人就搞出了GB2312,GBK这两个字符集,ascii用一个字节进行编码,我们汉字太多,因此我们用多个字节进行编码。
中国人搞一套,法国人搞一套,俄罗斯人又搞了一套,渐渐的,就乱套了。
干脆,搞一个大点的字符集,把这个世界上所有的字符都进行编码,然后大家就用这套编码来处理文本,这就是unicode字符集。
2.2 大一统的问题
unicode只是一个字符集,它规定了不同的字符在二进制上的表示形式,比如“升”这个汉字,它的unicode编码是 \u5347,5347是16进制,转换成成10进制是21319,转成二进制是101 0011 0100 0111,这一个汉字,至少需要2个字节来表示。
下面的代码,演示了获取一个汉字的编码内容
# 1 转成unicode
ch = '升'
ch_unicode = ch.encode('unicode_escape')
print(ch_unicode)
# 2 转成16进制形式
ch_hex = "0x" + str(ch_unicode,encoding='utf-8')[2:]
print(ch_hex)
# 3 转成10进制
ch_int = eval(ch_hex)
print(ch_int)
# 4 转成二进制
print(bin(ch_int))
程序运行结果
b'\\u5347'
0x5347
21319
0b101001101000111
unicode并没有规定这些字符所对应的二进制代码,但是并没有规定这些二进制代码该如何存储。这个汉字两个字节就能存储,但有些字符需要三个字节,像a这种字符,以前大家用ascii码的时候,用一个字节就能表示,在unicode里如果用两个或者更多字节表示,那么不是很浪费么,而且也与之前的ascii不兼容。
utf-8解决了unicode的编码问题,它是一种变长的编码方式,ascii码表里的字符仍然用一个字节来存储,一个汉字用三个字节来存储
ascii_a = 'a'
ascii_a_utf8 = ascii_a.encode(encoding='utf-8')
print(ascii_a_utf8, len(ascii_a_utf8))
ch = '升'
ch_utf8 = ch.encode(encoding='utf-8')
print(ch_utf8, len(ch_utf8))
程序运行结果
b'a' 1
b'\xe5\x8d\x87' 3
在python3中,字符串是以unicode编码的,当你想把一个字符串写入到磁盘上时,就必须指定用哪种编码方式进行存储,否则,就容易出错,比如下面的这段代码
with open('city', 'w') as f:
f.write('北京')
报的错误是
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
有了前面的内容做铺垫,你大概可以知道究竟发生了什么错误。
字符串采用的是unicode字符集,但是文件保存的时候,默认采用ascii编码,这就有问题了,ascii可以表示的范围太有限了,只有128个字符,可是汉字的unicode编码里很容就出现大于128的字节,这就是错误发生的原因,解决这个问题,可以采取下面两种方法
4.1 指定utf-8编码
with open('city', 'w', encoding='utf-8') as f:
f.write('北京')
4.2 以二进制的形式写入文件
with open('city', 'wb') as f:
f.write('北京'.encode('utf-8'))
这种方法虽然也行,但并不常用,因为这需要每次写入都对字符串进行utf-8编码,不如第一种方法简单高效