本文浅显易懂,绿色纯自然,手工制做,请放心阅读。python
编码问题是一个很大很杂的话题,要向完全的讲明白能够写一本书了。导致乱码的缘由不少,系统平台、编程语言、多国语言、软件程序支持、用户选择等均可能致使没法正确的解析编码。编程
致使乱码的主要缘由能够简单归结于文本的编码方式和解码方式不一样致使的。本文将经过在win7(zh-cn)系统下分析python2.7的编解码问题来简单窥探一下编码的冰山一角。网络
从此遇到编码问题时可以多一点分析解决思路,要是能起到一个抛砖引玉的做用,那就再好不过了。python2.7
物理存储介质上的基本存储单元只能有两种状态,使用0和1区分。一段连续的0,1序列能够表示特定的信息。编程语言
理论上任何字符均可以被惟一的一个连续0,1组成的bit序列表示。若是这样的一个bit序列也惟一的表明一个字符,那么咱们能够由此创建一个bit序列和字符之间的转换关系。学习
数字0 - 9和字母a - z(A - Z)等是人类可识别的字符,可是没法存入到计算机的存储介质中。编码
十进制 | 二进制(bit 序列) | 字符 |
48 | 0011 0000 | 0 |
65 | 0100 0001 | A |
97 | 0110 0001 | a |
为此,咱们将这些人类可识别的有意义的字符与特定0,1组成的bit序列创建起一一对应的关系。spa
由字符转成对应的bit序列的过程称为编码,将bit序列解释成对应的字符则称之为解码。插件
这样的一个bit序列能够用于存盘和网络传输等全部只能使用二进制表示的环境中。当须要阅读文件时再解码并在显示屏上显示出人类可识别的字符。3d
编码是人和机器之间的传递和表示信息的一种方式。
字符与bit序列之间的转换随之带来的两个问题是:
第一个问题能够用一个你们公认的标准来解决。
第二个问题则主要是节约的角度考虑,在能够表示特定字符集的状况下,须要用尽量短的二进制序列。
问题看似简单,但因为历史缘由,不一样国家不一样语种的编码标准并不相同。并且即便同一个标准也在不断的发展。
咱们常见的编码标准ASCII,UTF-8,Unicode,GBK(中文编码标准)等。
编码长度由编码方式决定,如ASCII码表示的字符都是一个字节(8 bits),Unicode编码的字符通常用两个字节。
同一种编码中不一样字符的编码长度也可能不一样。如UTF-8,对字符编码尽可能压缩以节俭空间,是一种“可变长编码”。
既然存在这么多种编码方式,那么对于一段通过编码的二进制序列,若是以其余的编码方式解码,显然会获得错误的解码信息。
这就是咱们所遇到的乱码问题。
那有没有一种编码方式能够将世界上全部的可表示字符都赋予一个惟一的编码?Unicode即是这样一种编码方式。
可是表示范围一应俱全的一个代价就是字符编码长度的增长。这样数据传输和存放时占用的网络和空间资源就会更多。
UTF-8是在Unicode基础上发展而来的可变长的编码方式。
Character | ASCII | Unicode | UTF-8 | GBK |
0 | 00110000 | 00000000 00110000 | 00110000 | 00110000 |
a | 01100001 | 00000000 01100001 | 01100001 | 01100001 |
字 | 没法表示 | 01011011 01010111 (u'\u5b57') | 11100101 10101101 10010111 ('\xe5\xad\x97') | 11010111 11010110 ('\xd7\xd6') |
注意:
上面的0是字符0,对应程序中“0”,而不是数字0。
数字是不使用字符编码的,数字可使用原码、反码和补码表示,在内存中通常使用补码表示,其字节序有大小端模式决定。
不一样的编码有各自的特色,下面是一种可能的字符编辑显示、加载传输和存储对应的各阶段编码。
因为Unicode能够和任何编码相互转换,能够借助Unicode实现不一样编码之间的变换。
python2中有两种表示字符串的类型:str 和 unicode。basestring是两者的共同基类。
Unicode对象包含的是unicode字符,而str对象包含的是字节(byte)。
汉字“中”的编码三种编码方式
'\xd6\xd0'(GBK) <==> u'\u4e2d'(UNICODE) <==> '\xe4\xb8\xad'(UTF-8)
在win7 + python2.7的环境下,Python 自带的IDE中输入的中文默认编码方式为GBK
字符串 '3132' 的编码是 '\x31\x32\x33\x34'
对应第二节表中的数值能够清楚的看到相同的字符对应的不一样的编码。
也能够说明UTF-8和GBK的编码兼容ASCII。而ASCII表示的字符在Unicode中则须要两个字节表示。
一个以ASCII编码的文档(注意不是ANSI编码)可使用UTF-8和GBK编码方式打开,而不能使用Unicode。
为了进一步验证,新建notepad++文档,选择编码方式为ANSI(即GBK),用喜欢的输入法以最快的方式键入汉字“中”。
使用HEX-Editor插件查看文档的GBK编码二进制表示:
“中”的UTF-8编码二进制表示:
“中”的Unicode(对应notepad++中的USC-2 Big Endian)编码二进制表示:
和python2中的结果相比能够看出
除了Unicode字符外,文件中存储的二进制数据和对应的编码是一一对应的。而这也验证了先前说的Unicode对象自己包含的并非字节。
\u是unicode编码的转义字符,上面起始的0xfeff能够看作unicode编码文件的一个起始标记。
例如
Unicode: 前两个字节为FFFE;
Unicode big endian: 前两字节为FEFF;
实际读取文件时会根据文件的前两个字节就能够断定出文件的具体格式。
使用gbk的方式读取utf-8编码文件(将utf-8文件编码方式改成ANSI),将会以GBK的方式解读字utf-8的节序列'\xe4\xb8\xad',结果以下:
反之,使用utf-8的方式读取GBK编码文件(以utf-8的方式解读字GBK的节序列'\xd6\xd0'):
下面再看一个编解码不一致致使的显示问题:
看一下咱们IDLE默认编码方式的确为GBK方式
下面是一个字符串,咱们平时常见的编码错误大概就是这种形式。
'宸茬敤鏃堕棿'
这个能够看作乱码,由于组合没有字面意思。其实这是一个utf-8编码按照gbk方式解码的结果,所以正确的方式先按照utf-8方式解码,而后编码成当前环境默认编码方式而后输出。
上面第二种错误的解码致使了更深一层次的乱码问题;解决这个问题是对字符串(非unicode)进行逆向的转换
>>> '瀹歌尙鏁ら弮鍫曟?'.decode('utf-8').encode('gbk').decode('utf-8').encode('gbk') '\xd2\xd1\xd3\xc3\xca\xb1\xbc\xe4' >>> print _ 已用时间
上面的转换过程以下:
'\xe5\xae\xb8\xe8\x8c\xac\xe6\x95\xa4\xe9\x8f\x83\xe5\xa0\x95\xe6\xa3\xbf', # 瀹歌尙鏁ら弮鍫曟? '\xe5\xb7\xb2\xe7\x94\xa8\xe6\x97\xb6\xe9\x97\xb4', #宸茬敤鏃堕棿 '\xd2\xd1\xd3\xc3\xca\xb1\xbc\xe4' #已用时间
在python文件中咱们可使用下面的方式标记文件的编码类型:
# -*- coding:utf-8 -*-
#coding:utf-8
关于python文件编码类型的声明能够参考官网: PEP 263
网上有不少人认为上面的编码声明定义了python文件的编码方式,但与其说定义python文件的编码方式,不如说是这仅仅是对python解释器的一种编码方式声明。即python 文件的编码声明 # -*- coding: utf-8 -*- 只是给python解释器看的。当python解释器执行py文件时会根据这个声明来解析文件编码格式。
也就是说这个声明和文件自己实际的文件编码没有关系。但二者最好保持一致,否者python解释器会以错误的方式去解码文件内容并执行。
上面的文件声明 # -*- coding:gbk -*- 误导python解释器以gbk的编码方式去解析一个utf-8编码的文件。
实际开发过程当中只须要统一将文件声明为utf-8便可。
再解释一下文件相关的编码概念:
文件自己的编码方式,该编码决定了文件数据以怎样的二进制格式保存在存储介质中。
文件内容显示的格式(解码方式),该编码方决定了文件内容是以怎样的解码方式被显示出来的。
通常文本显示类的程序打开一个文本时会根据文本文件起始字节判断该文本的编码方式,固然也能够有用户手动改变文件的解码方式。
若是文本程序或用户选择和编码方式不兼容的错误的解码方式,可能就会出现乱码。
可是对于可执行py文件而言,python解释器会以py文件开头的编码声明方式来解释文件内容,若是没有声明,python解释器会以默认的ASCII编码解析文件。
也就是只要python可执行文件的声明编码只要和实际文件的编码方式一致,就没有任何问题。所以可使用# -*- coding:gbk -*-做为声明,但考虑到兼容性和可移植性,最好编码和声明均采用utf-8。
以上结果均在特定的平台和环境下得出,仅为我的看法。若有错误欢迎指正,共同探讨学习。