文章目录
常见编码
-
GB2312是中国规定的汉字编码,也可以说是简体中文的字符集编码。GBK是GB2312的扩展,除了兼容GB2312外,它还能显示繁体中文,还有日文的假名。
-
CP936是指系统里第936号编码格式,即GB2312的编码,GBK为CP20936。中文版Windows中的CMD.exe,默认使用CP936,
-
UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,又称万国码,由Ken Thompson于1992年创建。现在已经标准化为RFC 3629。UTF-8用1到6个字节编码Unicode字符。用在网页上可以统一页面显示中文简体繁体及其它语言(如英文,日文,韩文)
Unicode与UTF-8的区别
Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
UTF-8是一种针对Unicode的可变长度编码规则,主要目的是提高传输效率和节省磁盘空间。目前Unicode由32Bit(4字节)表示,与字符一一对应。UTF-8编码规则
计算机通用字符编码工作方式
如上图所示,记事本将文件内容转换成Unicode编码进行处理,处理结束后再转换成UTF-8的编码存入文件。通常情况,在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。Unicode与字符一一对应,而UTF-8需要转换计算,在内存中使用Unicode,以空间换时间提高执行效率。网络和磁盘I/O延迟要远大于UTF-8的转换延迟,使用UTF-8节省带宽,保证数据的传输可靠性。
Python涉及的编码
- 文件编码
- 运行环境编码
- Python系统编码
- Python程序读取外部文件、网页的编码
文件编码有什么不同
test_utf8.py是一个以UTF-8编码保存的py文件,通过hex工具查看文件的底层编码,我们可以看到"张"的UTF-8编码为E5BCA0
。
# test_utf8.py
# -*- coding: UTF-8 -*-
s = '张'
print s
00000000h: 23 20 2D 2A 2D 20 63 6F 64 69 6E 67 3A 20 75 74 ; # -*- coding: ut
00000010h: 66 2D 38 20 2D 2A 2D 0D 0A 73 20 3D 20 27 [E5 BC ; f-8 -*-..s = '寮
00000020h: A0] 27 0D 0A 70 72 69 6E 74 20 73 ; ?..print s
新建相同内容的test_gb2312.py文件,以GB2312编码保存并重新打开,此时"张"的编码为D5C5
。比较后,发现ASCII的字符在GB2312和UTF-8的编码下保持一致,中文字符则有不同的编码。
00000000h: 23 20 2D 2A 2D 20 63 6F 64 69 6E 67 3A 20 75 74 ; # -*- coding: ut
00000010h: 66 2D 38 20 2D 2A 2D 0D 0A 73 20 3D 20 27 [D5 C5] ; f-8 -*-..s = '张
00000020h: 27 0D 0A 70 72 69 6E 74 20 73 ; '..print s
Windows cmd下运行test_utf8.py和test_gb2312.py
PS C:\Users\soga\Desktop> python .\test_utf8.py
寮
PS C:\Users\soga\Desktop> python .\test_gb2312.py
张
造成两个文件运行结果不同的原因是系统终端与字符串编码不一致。Python会试图把内部编码的字符串转化成当前执行程序的终端环境下所使用的编码方式(sys.stdout.encoding),调用print(sys.stdout.write)后输出。前面提到中文Windows下,cmd的环境编码为CP936。cmd以自己的编码打印UTF-8内容,必然会造成错误。Python可以通过locale查看运行环境的编码,一般情况下,cmd的编码与locale一致。
import locale
print(locale.getdefaultlocale())
Windows下可以通过chcp修改cmd的终端编码。修改终端编码为CP2093,运行example.py查看变化,此时Python的sys.output.encoding
与终端编码保持一致,系统环境编码保持不变。
# example.py
import locale
import sys
print (locale.getdefaultlocale())
print (sys.stdout.encoding)
# cmd.exe
PS C:\Users\soga\Desktop> chcp 20936
Active code page: 20936
PS C:\Users\soga\Desktop> python2 .\example.py
('zh_CN', 'cp936')
'cp20936'
为什么要在文件开头加入# -- coding: UTF-8 --`*
PEP263解释了编码注释的来源
This PEP proposes to introduce a syntax to declare the encoding of a python source file. The encoding information is then used by the python parser to interpret the file using the given encoding.
Python解析器使用给定的编码来解释文件,Python的默认解释编码为ASCII,因此不添加UTF-8或CP936声明,Python解释器在遇到中文时是无法翻译源代码的。
Python系统编码
查看Python系统编码,默认为ascii
import sys
print (sys.getdefaultencoding()) # 'ascii'
Python2.X中的字符串
- str:是字节串(container for bytes),由 Unicode 经过编码(encode)后的字节组成的。
- unicode:真正意义上的字符串,其中的每个字符用 Unicode 中对应的 Code Point 表示。
x_str由两个16进制数\xd5\xc5
表示,看起来很眼熟,跟我们前面展示的test_gb2312
内容一样。说明此时Python使用CP936
编码中文字符。x_utf的\u5f20
则是unicode中字符"张"的的代码点。
>>> x_str = '张'
>>> x_utf = u'张'
>>> x_str
'\xd5\xc5'
>>> x_utf
u'\u5f20'
>>> x_gb2312 = x_utf.encode('gb2312') # 与x_str相同
>>> x_gb2312
'\xd5\xc5'
总结,str类型就是unicode经过编码后的16进制字节数组。
unicode与str的转换
Python为两种类型的字符串提供了两个互相转换的方法。
S.encode([encoding[,errors]]) -> object
S.decode([encoding[,errors]]) -> object
上图中绿色线段标示的即为我们常用的转换方法,红色标示的转换在 python 2.x 中是合法的,不过没有什么意义,通常会抛出错误(可以参见What is the difference between encode/decode?)。下面是两种类型之间的转换示例:
zh_str = '中文'
zh_dec = zh_str.decode('gb2312')
print("%s %s %s %s" % (type(zh_str), '->', type(zh_dec), repr(zh_dec)))
zh_enc = zh_dec.encode('gb18030')
print("%s %s %s %s" % (type(zh_dec), '->', type(zh_enc), repr(zh_enc)))
# idle output
<type 'str'> -> <type 'unicode'> u'\u4e2d\u6587'
<type 'unicode'> -> <type 'str'> '\xd6\xd0\xce\xc4'
Python系统编码引起的异常
隐藏的解码
str和unicode类型都可以用来表示字符串,为了方便它们之间进行操作,Python并不要求在操作之前统一类型,所以下面的代码是合法的,并且能得到正确的输出:
>>> s = u'hello ' + 'John'
>>> s, type(s)
(u'hello John', <type 'unicode'>)
因为str类型是隐含有某种编码方式的字节码,所以Python内部将其解码为unicode后,再和unicode类型进行"+"操作,最后返回的结果也是unicode类型。第2步的解码过程是在幕后悄悄发生的,默认采用ascii来进行解码,可以通过 sys.getdefaultencoding() 来获取默认编码方式。Python之所以采用 ascii,是因为 ascii 是最早的编码方式,是许多编码方式的子集。不过正是这个不可见的解码过程,有时候会导致出乎意料的解码错误,考虑下面的代码:
>>> s = u'hello ' + '约翰'
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
s = u'hello ' + '约翰'
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd4 in position 0: ordinal not in range(128)
上面在字符串的"+"操作时,Python偷偷对"约翰"用ascii做解码操作,所以抛出了UnicodeDecodeError异常。其实上面操作等同于u'hello ' + '约翰'.decode('ascii')
。
隐藏的编码
与编码类似,在没有人为指定编码的情况下,Python也会偷偷的使用ascii对字符串编码。尝试直接解码str类型的字符串,一般情况下,会正确的解码为unicode字符串。
>>> s = 'John'.decode()
>>> s
u'John'
换成中文试试
>>> s = '约翰'.decode()
Traceback (most recent call last):
File "<pyshell#36>", line 1, in <module>
s = '约翰'.decode()
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd4 in position 0: ordinal not in range(128)
上面的错误提示信息'ascii' codec can't decode...
已经很明显的告诉我们,Python默认使用ascii
对字符串解码,正确的做法是指定合适的类型,比如'约翰'.decode('gb2312')
。
解决编码的常见方法
- 修改Python系统编码
在程序开头加上以下三行代码。实际使用不推荐这种方法,网上的评论显示其会产生bug,查看StackOverflow的讨论。
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
特别指出一点,sys中的setdefaultencoding()
在启动后就被删除,在导入sys后是找不到该方法的。删除代码的位置在/Lib/site.py下的main函数
def main():
...
if hasattr(sys, "setdefaultencoding"):
del sys.setdefaultencoding
- 人为指定编码
输入时,将外部文件或信息流转换成unicode处理。输出时根据外部的要求转化为相应的编码,尽量在程序内部统一使用unicode类型