上两天编写了个小工具程序,读取文件信息并输出适当数据到控制台。程序本身没什么难度,但是编码问题确实恶心到我了。同样的程序,在mac下跑没问题,在Windows下跑就有问题;在pycharm上面的终端上跑没问题,到系统自带的terminal上面跑又有问题。真是哔了狗了呀!
简单搜索下就可以发现在python下面确实很多人遇到或者曾经遇到过python的这个编码问题。
作为程序员,你一定遇到过中文编码的问题。
编码基础
就像不同的国家使用不同的语言一样,小猪面对不会中文的同学时也是一脸懵逼。计算机也是一样,不同的地区使用不同的语言,而计算机只认识0和1。怎么让这些0和1显示成人能理解的字符呢?
首先登场的是ASCII码。
ASCII
ASCII码,每一个字符都会对应一个ASCII码,占一个字节,早期的电脑是美国人发明的,所以最开始的时候ASCII码是只有127个的。例如0123456789a-zA-Z。发展到中国的时候,原本的这些ASCII码显然已经不够用了,所以中国制定了GB2312码,占两个字节。
可是这只解决了中文的问题,那韩文日文法文怎么办呢?每个国家都制定一个编码?那一个语言的输出到另外的国家的编码里显然会出现了所谓的乱码。所以,编码真是个让人头疼的问题啊。尤其是在编写python2程序的时候。
为了解决上述问题,另外一个名词就出现了:Unicode,占用两字节,目前大多数操作系统都支持该编码。
Unicode
Unicode(中文:万国码、国际码、统一码、单一码)是计算机科学领域里的一项业界标准。它对世界上大部分的文字系统进行了整理、编码,使得电脑可以用更为简单的方式来呈现和处理文字 –维基百科
字母A用ASCII编码是二进制的
01000001
,十进制的65;
字符0用ASCII编码是二进制的00110000
,十进制的48,注意字符’0’和整数0是不同的;
汉字中已经超出了ASCII编码的范围,用Unicode编码是十进制的20013,二进制的01001110 00101101。
你可以猜测,如果把ASCII编码的A用Unicode编码,只需要在前面补0就可以,因此,A的Unicode编码是00000000 01000001。
所以,Unicode其实是包含了ASCII码的表示范围的,只是如果ASCII码在不够使用的情况下使用Unicode来表示。在使用Unicode码的时候,本身能够使用ASCII码的数据只需在前面补足一个字节的00000000就可以了。
可是在早期的计算机系统当中,存储数据的空间是非常昂贵的。虽然是满足了所有字符能够表示的问题,但是多出来毫无意义的一个字节也是要占用空间的。
这个时候又出现了我们经常提到用到的UTF-8
编码.
UTF-8
他就是所谓的变长编码,当表示的字符能够用一个字节来表示是,他就占用一个字节,当当前字符需要使用两个字节的空间来表示时,他就占用两个字节,特殊情况下可能用到三个字节甚至四个字节。
所以在计算机的内存中,是使用0和1的ASCII码,当存储到硬盘或者需要显示的时候,程序将其转换成对应的UTF-8码。
python的编码
python可是在Unicode发布之前就已经出身了。所以他出身之后的事情在出身时当然是不知道的。
所以python2的默认编码为ascii,python3的默认编码为utf-81
2
3>>> import sys
>>> sys.getdefaultencoding\(\)
'ascii'
python提供了内置函数来查看字符的ASCII码!
1 | >>> ord('Z') |
在后来的版本中,python提供了对Unicode的支持,但是必须要在字符的前面加上字母u:
1 | >>> print(u"小猪") |
所以,字符u'\u5c0f\u732a'
和u"小猪"
对python来说是一样的。但是对于只看输出结果的人来说就不一样了,所以才会出现文章一开始的时候提到的让小猪头疼的输出乱码问题。
上面代码的第三部分是把字符’小猪’当做utf-8来打印了。所以在使用utf-8来decode的时候能够解析出来u'\u5c0f\u732a'
一般我们需要在python文件的开头标识当前文件使用哪个编码格式:
1 |
|
这样在打印中文的时候就不会报错:
1 |
|
可是在还有一些情况下,例如开头时说的,很多字符是从别的库里面引用进来的,这样我们并不能保证得到的每个字符串的编码都是一致的。这时候就要根据得到的字符的具体的编码来使用decode方法来进行解码然后来显示了。