Python中的字符编码是个老生常谈的话题,同行们都写过不少这方面的文章。有的人云亦云,也有的写得很深刻。近日看到某知名培训机构的教学视频中再次谈及此问题,讲解的仍是不尽人意,因此才想写这篇文字。一方面,梳理一下相关知识,另外一方面,但愿给其余人些许帮助。html
Python2的 默认编码 是ASCII,不能识别中文字符,须要显式指定字符编码;Python3的 默认编码 为Unicode,能够识别中文字符。python
相信你们在不少文章中都看到过相似上面这样“对Python中中文处理”的解释,也相信你们在最初看到这样的解释的时候确实以为明白了。但是时间久了以后,再重复遇到相关问题就会以为貌似理解的又不是那么清楚了。若是咱们了解上面说的默认编码的做用是什么,咱们就会更清晰的明白那句话的含义。程序员
须要说明的是,“字符编码是什么”,以及“字符编码的发展过程” 不是本节讨论的话题,这些内容能够参考我以前的 < <这篇文章> > 。编程
一个字符不等价于一个字节,字符是人类可以识别的符号,而这些符号要保存到计算的存储中就须要用计算机可以识别的字节来表示。一个字符每每有多种表示方法,不一样的表示方法会使用不一样的字节数。这里所说的不一样的表示方法就是指字符编码,好比字母A-Z均可以用ASCII码表示(占用一个字节),也能够用UNICODE表示(占两个字节),还能够用UTF-8表示(占用一个字节)。字符编码的做用就是将人类可识别的字符转换为机器可识别的字节码,以及反向过程。编程语言
UNICDOE才是真正的字符串,而用ASCII、UTF-八、GBK等字符编码表示的是字节串。关于这点,咱们能够在Python的官方文档中常常能够看到这样的描述"Unicode string" , " translating a Unicode string into a sequence of bytes"编辑器
咱们写代码是写在文件中的,而字符是以字节形式保存在文件中的,所以当咱们在文件中定义个字符串时被当作字节串也是能够理解的。可是,咱们须要的是字符串,而不是字节串。一个优秀的编程语言,应该严格区分二者的关系并提供巧妙的完美的支持。JAVA语言就很好,以致于了解Python和PHP以前我历来没有考虑过这些不该该由程序员来处理的问题。遗憾的是,不少编程语言试图混淆“字符串”和“字节串”,他们把字节串当作字符串来使用,PHP和Python2都属于这种编程语言。最能说明这个问题的操做就是取一个包含中文字符的字符串的长度:google
注意:Windows的cmd终端字符编码默认为GBK,所以在cmd输入的中文字符须要用两个字节表示编码
>>> # Python2 >>> a = 'Hello,中国' # 字节串,长度为字节个数 = len('Hello,')+len('中国') = 6+2*2 = 10 >>> b = u'Hello,中国' # 字符串,长度为字符个数 = len('Hello,')+len('中国') = 6+2 = 8 >>> c = unicode(a, 'gbk') # 其实b的定义方式是c定义方式的简写,都是将一个GBK编码的字节串解码(decode)为一个Uniocde字符串 >>> >>> print(type(a), len(a)) (<type 'str'>, 10) >>> print(type(b), len(b)) (<type 'unicode'>, 8) >>> print(type(c), len(c)) (<type 'unicode'>, 8) >>>
Python3中对字符串的支持作了很大的改动,具体内容会在下面介绍。翻译
先作下科普:UNICODE字符编码,也是一张字符与数字的映射,可是这里的数字被称为代码点(code point), 实际上就是十六进制的数字。code
Python官方文档中对Unicode字符串、字节串与编码之间的关系有这样一段描述:
Unicode字符串是一个代码点(code point)序列,代码点取值范围为0到0x10FFFF(对应的十进制为1114111)。这个代码点序列在存储(包括内存和物理磁盘)中须要被表示为一组字节(0到255之间的值),而将Unicode字符串转换为字节序列的规则称为编码。
这里说的编码不是指字符编码,而是指编码的过程以及这个过程当中所使用到的Unicode字符的代码点与字节的映射规则。这个映射没必要是简单的一对一映射,所以编码过程也没必要处理每一个可能的Unicode字符,例如:
将Unicode字符串转换为ASCII编码的规则很简单--对于每一个代码点:
将Unicode字符串转换为UTF-8编码使用如下规则:
简单总结:
可见,不管是编码仍是解码,都须要一个重要因素,就是特定的字符编码。由于一个字符用不一样的字符编码进行编码后的字节值以及字节个数大部分状况下是不一样的,反之亦然。
咱们都知道,磁盘上的文件都是以二进制格式存放的,其中文本文件都是以某种特定编码的字节形式存放的。对于程序源代码文件的字符编码是由编辑器指定的,好比咱们使用Pycharm来编写Python程序时会指定工程编码和文件编码为UTF-8,那么Python代码被保存到磁盘时就会被转换为UTF-8编码对应的字节(encode过程)后写入磁盘。当执行Python代码文件中的代码时,Python解释器在读取Python代码文件中的字节串以后,须要将其转换为UNICODE字符串(decode过程)以后才执行后续操做。
上面已经解释过,这个转换过程(decode,解码)须要咱们指定文件中保存的字节使用的字符编码是什么,才能知道这些字节在UNICODE这张万国码和统一码中找到其对应的代码点是什么。这里指定字符编码的方式你们都很熟悉,以下所示:
# -*- coding:utf-8 -*-
那么,若是咱们没有在代码文件开始的部分指定字符编码,Python解释器就会使用哪一种字符编码把从代码文件中读取到的字节转换为UNICODE代码点呢?就像咱们配置某些软件时,有不少默认选项同样,须要在Python解释器内部设置默认的字符编码来解决这个问题,这就是文章开头所说的“默认编码”。所以你们所说的Python中文字符问题就能够总结为一句话:当没法经过默认的字符编码对字节进行转换时,就会出现解码错误(UnicodeEncodeError)。
Python2和Python3的解释器使用的默认编码是不同的,咱们能够经过sys.getdefaultencoding()来获取默认编码:
>>> # Python2 >>> import sys >>> sys.getdefaultencoding() 'ascii' >>> # Python3 >>> import sys >>> sys.getdefaultencoding() 'utf-8'
所以,对于Python2来说,Python解释器在读取到中文字符的字节码尝试解码操做时,会先查看当前代码文件头部是否有指明当前代码文件中保存的字节码对应的字符编码是什么。若是没有指定则使用默认字符编码"ASCII"进行解码致使解码失败,致使以下错误:
SyntaxError: Non-ASCII character '\xc4' in file xxx.py on line 11, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
对于Python3来说,执行过程是同样的,只是Python3的解释器以"UTF-8"做为默认编码,可是这并不表示能够彻底兼容中文问题。好比咱们在Windows上进行开发时,Python工程及代码文件都使用的是默认的GBK编码,也就是说Python代码文件是被转换成GBK格式的字节码保存到磁盘中的。Python3的解释器执行该代码文件时,试图用UTF-8进行解码操做时,一样会解码失败,致使以下错误:
SyntaxError: Non-UTF-8 code starting with '\xc4' in file xxx.py on line 11, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
-*- coding:utf-8 -*-
其实Python3中对字符串支持的改进,不只仅是更改了默认编码,而是从新进行了字符串的实现,并且它已经实现了对UNICODE的内置支持,从这方面来说Python已经和JAVA同样优秀。下面咱们来看下Python2与Python3中对字符串的支持有什么区别:
Python2中对字符串的支持由如下三个类提供
class basestring(object) class str(basestring) class unicode(basestring)
执行help(str)和help(bytes)会发现结果都是str类的定义,这也说明Python2中str就是字节串,然后来的unicode对象对应才是真正的字符串。
#!/usr/bin/env python # -*- coding:utf-8 -*- a = '你好' b = u'你好' print(type(a), len(a)) print(type(b), len(b))
输出结果:
(<type 'str'>, 6) (<type 'unicode'>, 2)
Python3中对字符串的支持进行了实现类层次的上简化,去掉了unicode类,添加了一个bytes类。从表面上来看,能够认为Python3中的str和unicode合二为一了。
class bytes(object) class str(object)
实际上,Python3中已经意识到以前的错误,开始明确的区分字符串与字节。所以Python3中的str已是真正的字符串,而字节是用单独的bytes类来表示。也就是说,Python3默认定义的就是字符串,实现了对UNICODE的内置支持,减轻了程序员对字符串处理的负担。
#!/usr/bin/env python # -*- coding:utf-8 -*- a = '你好' b = u'你好' c = '你好'.encode('gbk') print(type(a), len(a)) print(type(b), len(b)) print(type(c), len(c))
输出结果:
<class 'str'> 2 <class 'str'> 2 <class 'bytes'> 4
上面提到,UNICODE字符串能够与任意字符编码的字节进行相互转换,如图:
那么你们很容易想到一个问题,就是不一样的字符编码的字节能够经过Unicode相互转换吗?答案是确定的。
字节串-->decode('原来的字符编码')-->Unicode字符串-->encode('新的字符编码')-->字节串
#!/usr/bin/env python # -*- coding:utf-8 -*- utf_8_a = '我爱中国' gbk_a = utf_8_a.decode('utf-8').encode('gbk') print(gbk_a.decode('gbk'))
输出结果:
我爱中国
字符串-->encode('新的字符编码')-->字节串
#!/usr/bin/env python # -*- coding:utf-8 -*- utf_8_a = '我爱中国' gbk_a = utf_8_a.encode('gbk') print(gbk_a.decode('gbk'))
输出结果:
我爱中国
最后须要说明的是,Unicode不是有道词典,也不是google翻译器,它并不能把一个中文翻译成一个英文。正确的字符编码的转换过程只是把同一个字符的字节表现形式改变了,而字符自己的符号是不该该发生变化的,所以并非全部的字符编码之间的转换都是有意义的。怎么理解这句话呢?好比GBK编码的“中国”转成UTF-8字符编码后,仅仅是由4个字节变成了6个字节来表示,但其字符表现形式还应该是“中国”,而不该该变成“你好”或者“China”。
前面花了很大的篇幅介绍概念和理论,后面注重实践,但愿对他人有所帮助。