Python学习之路day3-字符编码与转码

1、基础概念

  • 字符与字节

    字符是相对于人类而言的可识别的符号标识,是一种人类语言,如中文、英文、拉丁文甚至甲骨文、梵语等等。
    字节是计算机内部识别可用的符号标识(0和1组成的二进制串,机器语言),属于机器语言。
    人与计算机交互就须要在人类语言和机器语言之间来回转换,所以当把各类各样的字符存储或输入到计算机时,最终都必须以字节形式来表示;反之当计
算机输出相应信息给人类用户时,最终也须要以人类可识别的字符形式来传递。
    综上所述,字符与人类更为接近,而字节则与计算机(机器)更为接近。
   
上面的概念区分看起来很是清楚了,但实际应用尤为是在进行python开发又很容易犯迷糊,各类编码什么的一旦有些许模糊就会出现意想不到的结果。那么怎么区分字符与字节呢?

    通用原则:
    Unicode才是真正的字符串,而ASCII、UTF-八、GBK等编码格式表示的都是字节码。
html

     缘由是:字符串是由字符构成,字符在计算机硬件中经过二进制形式存储,这种二进制形式就是字节码(编码)。若是直接使用 “字符串↔️字符↔️二进制表示(编码)”,会增长不一样类型编码之间转换的复杂性。因此引入了一个抽象层,“字符串↔️字符↔️与存储无关的表示↔️二进制表示(编码)” ,这样,能够用一种与存储无关的形式表示字符,不一样的编码之间转换时能够先转换到这个抽象层,而后再转换为其余编码形式。在这里,unicode 就是 “与存储无关的表示”,是一种字符集,而ASCII、UTF-八、GBK等就是字符串对应的“二进制表示”。(以上文字绝大部分引自
连接:https://www.zhihu.com/question/23374078/answer/28710945,做者:flyer,原文博客成死连接了 https://link.zhihu.com/?target=http%3A//flyer103.diandian.com/post/2014-03-09/40061199665
    所以,Unicode解决了人类识别计算机语言的兼容性和可用性问题,是一种字符串(只是一种字符集,而没有编码格式),而ASCII、UTF-八、GBK等是解决字符信息(Unicode)如何在计算机内部存储和表示的,是二进制形式的字节码。
   
关于这点,咱们能够在Python的官方文档中常常能够看到这样的描述"Unicode string" , " translating a Unicode string into a sequence of bytes",咱们写代码是写在文件中的,而字符是以字节形式保存在文件中的,所以当咱们在文件中定义个字符串时被当作字节串也是能够理解的。可是,咱们须要的是字符串,而不是字节串。(这段文字引用自http://www.cnblogs.com/yyds/p/6171340.html)Python2.0没有对此做严格区分,好在Python 3里已经开始严格区分了,而且默认的字符集就是Unicode。换句话讲,咱们经过pycharm进行python 3.0开发时,编辑器里呈现的代码都是字符串,都是Unicode,不管咱们在文件头声明使用什么文件格式(文件头的申明决定了源代码文件以什么样的二进制格式存储)。node

  • 编码

    前面章节所述从人类可识别的字符到计算机可识别处理(存储传输)的字节的转换,就是一个编码的过程,即encode。而ASCII、UTF-八、GBK等,则决定了如何把字符转换为二进制字节码(转换规则)和转换后的二进制码的内容,因此它们是具体的编码格式(同一个符号能够有多种二进制字节码的表示)。借用网上某大神搬出来的通讯理论强化一下,unicode是信源编码,对字符集数字化;utf8是信道编码,为更好的存储和传输。python

  • 解码

    解码就是编码的逆过程,把计算机中存储运行的二进制字节码转换为字符的过程,即decode。
简单总结:
     编码(encode):将Unicode字符串转换特定字符编码对应的字节串的过程和规则
     解码(decode):将特定字符编码的字节串转换为对应的Unicode字符串的过程和规则

可见,不管是编码仍是解码,都须要一个重要因素,就是特定的字符编码。由于一个字符用不一样的字符编码进行编码后的字节值以及字节个数大部分状况下是不一样的,反之亦然。

    仍是来一张图示吧:
linux

  image

  • Python源代码的执行过程

    首先明确一下基本概念,咱们借助Pycharm来进行Python程序开发时,须要与编辑器和解释器打交道,Pycharm做为IDE就是编辑器,提升咱们编写代码的效率;而咱们安装的来自官方的Python程序(在Pycharm中运行时显示的python.exe)则是解释器。
    结合上面章节对字符串和字节码相互转换的过程讲解,能够肯定在开发过程当中会在如下两个地方用到字符编码:
windows

  1. 磁盘文件的存储写入和读取
    这是对源代码文件编写后的保存和读取处理过程,由编辑器Pycharm自动完成。保存时须要encode,读取时则须要decode,这是由编辑器指定的工程或文件的字符编码决定的(Pycharm中仍是推荐用默认的UTF-8)。
  2. 程序执行时的输入和输出
    这是程序执行时的信息的表示过程,由解释器Python.exe完成。同理输入时须要encode(转换成机器码),输出时须要decode。请注意,这里的程序执行时的输出不只包括程序运行后的各类print,更重要的是包括程序启动运行时Python解释器将源代码文件中的字节码读取后转换为Unicode(decode)的过程,在此基础上Python解释器才会进行后续的处理(如print一个字符串或者变量)。这个过程须要咱们指定源代码文件中保存字节时所使用的字符编码。
    总结以下:
    经过文件头指定字符编码:
      1 # -*- coding:utf-8 -*-

        Python源代码执行过程:
        image
         图片和上述思想主要摘自http://www.cnblogs.com/yyds/p/6171340.html
         补充一下,Python解释器经过指定的字符编码把字节码转换为Unicode只是程序执行的刚刚开始,后续解释器还须要把Unicode字符串翻译为c代码(默认的cpython),而后转换成二进制流经过操做系统的相应接口调用CPU来执行二进制数据(机器只识别二进制数据),最后以Unicode方式返回咱们须要的结果。
编辑器

2、Unicode的由来与UTF-8的关系

     理清了字符串和字节码各自的概念和转换过程, 如今不得不引入Unicode的由来以及它与UTF-8的关系了。
     Unicode的由来
    
众所周知计算机由美国人发明, 因为英文字母相对固定简单, 美国大叔仅仅经过不到128位的ASCII就解决了字符编码问题. 随着计算机技术的不断发展和推广,其余国家也陆续开始使用计算机.遗憾的是对于非英语母语国家而言,因为母语相对于英语字母的复杂性和多样性,不到128位的ASCII无法扛起字符编码的重担.因而不少多家都结合本身的母语开发了适用于本国语言的字符编码体系,咱们如今熟知的GBK,GB2312等国标都是那个时期的产物。
     因为字符编码的各自为政,使得不一样国家之间的交流变得很不畅,一不当心就踩到乱码了. 因而ISO集全球之力量,通过充分调研和科学设计,制定了能够一应俱全、能搞定世界上基本全部语言编码问题的Unicode,人称万国码。
     请注意,Unicode自己并非一种字符编码,而是一种字符集。若是直接使用 “字符串↔️字符↔️二进制表示(编码)”,会增长不一样类型编码之间转换的复杂性。因此引入了一个抽象层,“字符串↔️字符↔️与存储无关的表示↔️二进制表示(编码)” ,这样,能够用一种与存储无关的形式表示字符,不一样的编码之间转换时能够先转换到这个抽象层,而后再转换为其余编码形式。在这里,unicode 就是 “与存储无关的表示”,是一种字符集,而ASCII、UTF-八、GBK等就是字符串对应的“二进制表示”。
函数

     Unicode与UTF-8的关系
    
不少地方会很简单地讲UTF-8就是Unicode的压缩和优化版,通过前面章节的分析咱们发现这种说法不严谨。仍是先复习下通讯理论,unicode是信源编码,对字符集数字化;utf8是信道编码,为更好的存储和传输。Unicode是一张字符与数字的映射,可是这里的数字被称为代码点(code point), 实际上就是十六进制的数字。Python官方文档中对Unicode字符串、字节串与编码之间的关系有这样一段描述:
     Unicode字符串是一个代码点(code point)序列,代码点取值范围为0到0x10FFFF(对应的十进制为1114111)。这个代码点序列在存储(包括内存和物理磁盘)中须要被表示为一组字节(0到255之间的值),而将Unicode字符串转换为字节序列的规则称为编码

     简单来说,Unicode字符集能够将字符用必定范围的数字来进行数字化,至于怎么把数字化表示为二进制,一千我的眼里有一千个哈姆雷特,这个过程就交给字符编码来显神通了,UTF-8恰好是其中一种且通用性很强的一种。所以能够讲utf8是对unicode字符集进行编码的一种编码方式,是对unicode字符集的二进制体现;Unicode是解决如何向人类展示可被识别的字符问题,而UTF-8是解决如何将人类可识别的字符转换为可被机器识别的字节码问题。
post

3、Python中的字符编码

  • 默认字符编码

    设想一个问题,若是咱们在编写Python代码文件时没有指定编码格式,程序在执行时解释器又怎么把字节码转换成Unicode呢?这里由Python中默认的字符编码来决定。当咱们的默认字符编码与编辑器里设置的字符编码不一致时,可经过文件头来进行字符编码格式的声明来解决,而若是此时没有文件头里面的声明,则会直接致使解码失败。Python2中解释器默认的字符编码是ASCII,Python3中解释器默认的字符编码是UTF-8。这就是Python2里默认状况下不支持中文字符的缘由(没有经过文件头申明文件编码就调用默认的ASCII进行解码,显然ASCII码里没有中文对应的字符)。测试

    Python解释器的默认编码可经过如下方式确认:优化

[root@node1 ~]# python
Python 2.6.6 (r266:84292, Aug 18 2016, 15:13:37)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-17)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getdefaultencoding()
'ascii'
>>>

C:\Users\Beyondi>python
Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:18:55) [MSC v.1900 64 bit (AM
D64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'
>>>

Python2下未声明字符编码状况下对中文字符的支持状况:

  1 [root@node1 python]# cat test.py
  2 #! /usr/bin/env python
  3 s='你好,世界!'
  4 print(s)
  5 [root@node1 python]# python test.py
  6   File "test.py", line 2
  7 SyntaxError: Non-ASCII character '\xe4' in file test.py on line 2, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

上面的报错很明显了,第2行中须要打印的字符串ASCII不支持,而且也没有对字符编码进行显示声明。
如今在文件头增长字符编码的显示声明:

  1 [root@node1 python]# cat test.py
  2 # -*- coding:utf-8 -*-
  3 #! /usr/bin/env python
  4 s='你好,世界!'
  5 print(s)
  6 [root@node1 python]# python test.py
  7 你好,世界!

增长正确的字符编码声明后,程序在执行时解释器会经过指定的字符编码UTF-8来对保存的字节码进行解码,而后进行后续的print处理,因为Linux环境下默认的字符编码也是UTF-8,因此中文字符就能正常显示了。若是把这段程序放到windows下运行(Python3解释器),状况又是怎样的呢?
经过记事本复制代码后保存为test.py,而后在cmd下运行:

  1 D:\python\S13\Day3>type test.py
  2 # -*- coding:utf-8 -*-
  3 #! /usr/bin/env python
  4 s='你好,世界!'
  5 print(s)
  6 D:\python\S13\Day3>python test.py
  7   File "test.py", line 3
  8 SyntaxError: (unicode error) 'utf-8' codec can't decode byte 0xc4 in position 0:
  9  invalid continuation byte

从输出结果来看程序仍是报错了,何故?咱们不是在文件头中声明了字符编码了吗?这里的问题在于咱们经过记事本保存代码文件时默认的字符编码是windows中文版下的GBK,而程序执行时Python解释器会用咱们在文件头中声明的UTF-8去解码成Unicode来进行后续的处理,用UTF-8去解码GBK编码,固然不能解了。因此要注意的是,须要保持解释器的字符编码与编辑器的字符编码一致。因此强烈建议在Pycharm中把默认的字符编码设置为UTF-8,省得给本身挖坑。
实际测试代表Pycharm是一个比较智能的IDE,若是经过页面右下角的按钮改变了文件编码,运行程序时文件头部的编码声明会自动调整以保持二者一致,此时你会发现右下角的改变文件编码按钮会有以下提示:
image

此时咱们须要把声明的字符编码改为与编辑器一致的字符编码GBK便可(或者修改编辑器保存文件时的字符编码也OK,总之要保持一致):

  1 D:\python\S13\Day3>type test.py
  2 # -*- coding:GBK -*-
  3 #! /usr/bin/env python
  4 s='你好,世界!'
  5 print(s)
  6 D:\python\S13\Day3>
  7 D:\python\S13\Day3>
  8 D:\python\S13\Day3>python test.py
  9 你好,世界!
 10 
 11 D:\python\S13\Day3>

补充一点,这里若是把须要打印输出的字符串内容改成英文字符,那么不修改文件头部的字符编码也能够正常输出:

  1 D:\python\S13\Day3>type test.py
  2 __author__ = 'Maxwell'
  3 #!/usr/bin/env python
  4 # -*- coding: utf-8 -*-
  5 s = 'Hello, world!'
  6 print(s)
  7 D:\python\S13\Day3>
  8 D:\python\S13\Day3>
  9 D:\python\S13\Day3>python test.py
 10 Hello, world!

这是由于英文字符在UTF-8和GBK下的二进制码是相同的。

  1 __author__ = 'Maxwell'
  2 #!/usr/bin/env python
  3 # -*- coding: utf-8 -*-
  4 s = 'Hello, world!'
  5 print(s.encode('utf-8'))
  6 print(s.encode('GBK'))
  7 print(type(s.encode('utf-8')))
  8 print(type(s.encode('GBK')))
  9 
 10 
 11 输出:
 12 b'Hello, world!'
 13 b'Hello, world!'
 14 <class 'bytes'>  #注意encode后类型变为二进制了
 15 <class 'bytes'>

因此,除非特别须要,强烈建议在代码一向保持用英文字符。

        综上所述,强烈建议遵循如下原则进行Python开发:

    1. 将IDE的字符编码设置为UTF-8
    2. 习惯性地在代码文件头部分明确声明使用UTF-8字符编码 -*- coding:utf-8 –*-
      这样能够保持程序对Python2解释器的良好兼容性。
  • Python 2 VS Python 3
    从事Python开发必定要了解Python2与Python3的差别性,在此从字符编码相关的角度略微展开。
    首先,Python2的默认编码是ASCII,而Python3的默认编码是UTF-8。
    其次,Python2和Python3在字符串与字节码的处理上大相径庭
    Python2中字符串有str和Unicode两种类型,但str表示的是通过各类字符编码编码后的二进制字节码,Unicode则是没有编码的标准文本。Unicode通过编码转换为为str,str通过解码后还原成Unicode。这里把字符串和字节码混在一块儿了。
    Python3中str类型即为Unicode字符串,单独设立的bytes表示通过编码处理后的二进制字节码。所以Python3中对字符串和字节码做了严格的区分。str通过编码后转换为bytes类型的字节码,反过来bytes类型的字节码通过解码则能够还原为Unicode字符串。
    仍是来一张形象的图示吧:
    Horizontal Cross Functional Template (1)

    经过上图可看出,Python3中的str相似于Python2中的Unicode,而Python3中的bytes则相似于Python2中的str,但请注意仅仅是相似而不是等同于,由于还存在其余层面的区别:
    (1)Python2中可直接查看str的Unicode字节序列
      1 [root@node1 python]# python
      2 Python 2.6.6 (r266:84292, Aug 18 2016, 15:13:37)
      3 [GCC 4.4.7 20120313 (Red Hat 4.4.7-17)] on linux2
      4 Type "help", "copyright", "credits" or "license" for more information.
      5 >>> s=u'你好,世界!'
      6 >>> s
      7 u'\u4f60\u597d\uff0c\u4e16\u754c!'

    (2)Python3中open函数加上了encoding参数,默认传递的字符编码是UTF-8,对文件进行read或write时,只接受包含Unicode的字符串。对于二进制文件,则须要以rb,wb或ab模式打开。
    借用大神的一张形象展现一下(引用自 http://www.cnblogs.com/yuanchenqi/articles/5956943.html):
    image

4、实战字符编码转码

    通过前面章节的理论铺垫,是时候来展开字符编码与转码的重点了。
    先从Python3开始:
    前面章节阐述过,Unicode是一个抽象层,与存储无关的表示,同时又一应俱全,能够很方便地与其余字符编码进行转换,所以它能够做为中间代理人来进行不一样编码之间的转换,以简化不一样编码之间直接转换的复杂度。实际应用中正是这样:

 
    经过例子来实战一下:

  1 __author__ = 'Maxwell'
  2 #!/usr/bin/env python
  3 # -*- coding: utf-8 -*-
  4 s = '你好,世界!'
  5 print(s.encode('utf-8'))
  6 print(s.encode('GBK'))
  7 s_to_gbk = s.encode('GBK')
  8 print(s_to_gbk.decode('GBK').encode('utf-8'))
  9 print(s_to_gbk.decode('GBK').encode('utf-8').decode('utf-8')) #连续转码处理,GBK解码后以utf-8方式编码,最后解码还原成Unicode
 10 
 11 输出:
 12 b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c\xef\xbc\x81'
 13 b'\xc4\xe3\xba\xc3\xa3\xac\xca\xc0\xbd\xe7\xa3\xa1'
 14 b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c\xef\xbc\x81'
 15 你好,世界!

  注意:
  1. 以上程序输出代表,Unicode字符串通过编码处理后当即变成bytes类型(输出结果以b开头,固然也能够经过type函数来验证肯定),须要打印时必须解码转换为Unicode字符串;
  2. 文件头部的显式字符编码声明仅仅决定了Python解释器在执行程序的开始阶段,会经过什么字符编码去打开解码保存的二进制字节码代码文件,它不改变程序代码中定义的str类型是Unicode字符串这一事实(此时定义的字符串s仍然能够被各类encode)。Python3中内嵌原生地支持Unicode,定义的str类型默认就是Unicode字符串。

  Python2版本下的状况:

  1 [root@node1 python]# python
  2 Python 2.6.6 (r266:84292, Aug 18 2016, 15:13:37)
  3 [GCC 4.4.7 20120313 (Red Hat 4.4.7-17)] on linux2
  4 Type "help", "copyright", "credits" or "license" for more information.
  5 >>> s='你好,世界!'
  6 >>> s_to_unicode=s.decode('utf-8')
  7 >>> s_to_gbk=s_to_unicode.encode('GBK')
  8 >>> print(type(s))
  9 <type 'str'>
 10 >>> print(type(s_to_unicode))
 11 <type 'unicode'>
 12 >>> s
 13 '\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c!'
 14 >>> s_to_gbk
 15 '\xc4\xe3\xba\xc3\xa3\xac\xca\xc0\xbd\xe7!'
 16 >>> print(s)
 17 你好,世界!
 18 >>> print(s_to_gbk.decode('GBK'))
 19 你好,世界!
 20 >>> print(type(s_to_gbk.decode('GBK'))) # decode以后类型变为Unicode
 21 <type 'unicode'>

Python2下的字符串是二进制字节码形式,能够经过str= u’Hello,world!’直接转换为Unicode。打印时能够对str自己打印,也能够对其转换后的Unicode打印,由于Python2下没有对字节和字符做严格区分。

  1 >>> s='你好,世界!'
  2 >>> print(s)
  3 你好,世界!
  4 >>> h=u'你好,世界!'
  5 >>> print(h)
  6 你好,世界!
  7 >>> h
  8 u'\u4f60\u597d\uff0c\u4e16\u754c!'
  9 >>> s
 10 '\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c!'
 11 >>> print(s.decode('utf-8'))
 12 你好,世界!
相关文章
相关标签/搜索