Python 字符编码

107 阅读8分钟

字符编码涉及大量理论知识,此文只做初步学习之整理,针对实际应用可直接看第三部分简单总结。

字符串类型、文本文件的内容都是由字符组成的,但凡涉及到字符的存取,都需要考虑字符编码的问题。

一、理论基础

  1. 什么是字符编码?

    人类在与计算机进行交互时,用的都是人类能读懂的字符,如中文字符、英文字符、数字字符等,而计算机只能识别二进制数。毫无疑问,由人类的字符到计算机中的数字,必须经历一个==“ 字符 → 翻译 → 数字 ”==的过程。而翻译的过程必须参照一个特定的标准,该标准称之为==字符编码表==,该表上存放的就是字符与数字一一对应的关系。

  2. Unicode(万国码)

    字符编码表多种多样,比如全英文的“ASCLL表”、包含中文英文的“GBK表”、包含日文英文的“Shift_JIS表”、包含韩文英文的“Euc-kr表”等等。==文件在保存时(存到硬盘里)是有编码格式的,只有当保存时使用的编码格式与读取时使用的编码格式相同才能正确读取文件,不同时就会所产生乱码的问题。==一句话概括乱码问题的原因:诸侯割据、天下大乱,

    我们希望计算机允许我们输入万国字符均可识别、不乱码,所以我们制定了一个==兼容万国字符的编码表,即Unicode码表。==

    Unicode码的两大特点:

    存在所有语言中的所有字符与数字的一一对应关系,即兼容万国字符。

    与其他传统的字符编码的二进制数都有对应关系,即与其他编码有相对应的映射/转换关系。

    很多地方或老的系统、应用软件仍会采用各种各样传统的编码,这是历史遗留问题。此处需要强调:==软件文件是存放于硬盘的,而运行软件是要将软件的程序文件和数据文件加载到内存的,==面对硬盘中存放的各种传统编码的软件,想让我们的计算机能够将它们全都正常运行而不出现乱码,==内存中必须有一种兼容万国的编码==,并且该编码需要与其他编码有相对应的映射/转换关系,这就是unicode的第二大特点产生的缘由。

    GBK、Shift-JIS等传统编码表是针对于存储在硬盘的保存文件,Unicode针对的是在内存上的统一编码。 可用图表示:

    也就是说,==文本编辑器输入任何字符都是最先存在于内存中,是unicode编码的;存放于硬盘时则可以转换成其他任意编码,只要该编码可以支持相应的字符。==

  3. UTF-8(Unicode Transformation Format)

    虽然unicode可以让电脑读取不同编码的文件,但还是有遗留问题。比如如果保存到硬盘的是GBK格式二进制,当初用户输入的字符就只能是中文或英文。如果我们输入的字符中包含多国字符,那么该如何处理?

    ​ 理论上是可以将内存中unicode格式的二进制直接存放于硬盘中的,但由于unicode固定使用两个字节来存储一个字符,如果多国字符中包含大量的英文字符时,使用unicode格式存放会额外占用一倍空间(英文字符其实只需要用一个字节存放即可),然而空间占用并不是最致命的问题,最致命地是当我们由内存写入硬盘时会额外耗费一倍的时间,所以将内存中的unicode二进制写入硬盘或者基于网络传输时必须将其转换成一种精简的格式,这种格式即utf-8(全称Unicode Transformation Format,即unicode的转换格式)。

    ==即 多国字符 ——√—》内存(unicode格式的二进制)——√—》硬盘(utf-8格式的二进制)==

    在UTF-8中,一个英文占1Byte,一个汉字占3Bytes。

    那为何在内存中不直接使用utf-8呢?

    ​ unicode更像是一个过渡版本(重要的是跟传统编码有转换关系),我们新开发的软件或文件存入硬盘都采用utf-8格式,等过去几十年,所有老编码的文件都淘汰掉之后,会出现一个令人开心的场景,即硬盘里放的都是utf-8格式,此时unicode便可以退出历史舞台,内存里也改用utf-8,天下重新归于统一。

  4. 文本文件存取乱码问题

    存乱了:解决方法是,编码格式应该设置成支持文件内字符串的格式。

    取乱了:解决方法是,文件是以什么编码格式存入硬盘的,就应该以什么编码格式读入内存。

二、实际应用

  1. python解释器执行test.py文件的流程

    1. 阶段1:启动python解释器,此时就相当于启动了一个文本编辑器
    2. 阶段2:python解释器相当于文本编辑器,从硬盘上将test.py文件的内容读入到内存中
    3. 阶段3:python解释器解释执行刚刚读入的内存的内容,开始识别python语法
  2. python解释器执行文件的前两个阶段

    执行py文件的前两个阶段就是python解释器读文本文件的过程,与文本编辑读文本文件的前两个阶段没人任何区别,==要保证读不乱码,则必须将python解释器读文件时采用的编码方式设置为文件当初写入硬盘时的编码格式==,如果没有设置,python解释器则才用默认的编码方式,==在python3中默认为utf-8,在python2中默认为ASCII==,我们可以通过指定文件头来修改默认的编码。

    在文件首行写入包含#号在内的以下内容:

    # coding: 当初文件写入硬盘时采用的编码格式
    

    解释器会先用默认的编码方式读取文件的首行内容,由于首行是纯英文组成,而任何编码方式都可以识别英文字符。

  3. python解释器执行文件的第三个阶段

    设置文件头的作用是保证运行python程序的前两个阶段不乱码,经过前两个阶段后py文件的内容都会以unicode格式存放于内存中。

    在经历第三个阶段时开始识别python语法,当遇到特定的语法name = '张三'(代码本身也都全都是unicode格式存的)时,需要申请内存空间来存储字符串 '张三' ,这就又涉及到应该以什么编码存储 '张三' 的问题了。

    ==在Python3中,字符串类的值都是使用unicode格式来存储。==

    由于Python2的盛行是早于unicode的,因此在Python2中是按照文件头指定的编码来存储字符串类型的值的(如果文件头中没有指定编码,那么解释器会按照它自己默认的编码方式来存储 '张三' ),所以,这就有可能导致乱码问题。

    python3的str类型默认直接存成unicode格式,无论如何都不会乱码。python2后推出了一种补救措施,就是在字符串类型前加u,则会将字符串类型强制存储unicode,保证加了u的str类型不乱码。这就与python3保持一致了,对于unicode格式无论丢给任何终端进行打印,都可以直接对应字符不会出现乱码问题。

    # coding:utf-8
    x = u'张三' # 即便文件头为utf-8,x的值依然存成unicode
    

    ==所以如果要在python2环境中运行程序,程序中的字符串前面就要加上“u”,来解决不同终端上可能存在的乱码问题。==

    编码与解码

    ​ 编码:

    # 1、unicode格式------编码encode-------->其它编码格式
    x = '上'  # 在python3在'上'被存成unicode
    res = x.encode('utf-8')
    print(res, type(res))  # unicode编码成了utf-8格式,而编码的结果为bytes类型,可以当作直接当作二进制去使用
    # (b'\xe4\xb8\x8a', <class 'bytes'>)
    

    ​ 解码:

    # 2、其它编码格式------解码decode-------->unicode格式
    res.decode('utf-8')
    print(res, type(res))  # b'\xe4\xb8\x8a' <class 'bytes'>
    

    注意:编码是什么格式,解码就要写什么格式

三、简单总结

  • 直接了当的说:以后写程序就无脑用utf-8编码即可。

  • 要保证前两个阶段不乱码,python3什么都不用做,而python2需要在程序头进行声明(声明指定的编码需要与文件存储时采用的编码一致)。第三个阶段语法,python3什么都不用做,而python2需要在定义字符串时前面加“u”。

  • 所以,当python3程序需要移植到python2环境上,首先程序头加声明,然后字符串定义加“u”。

  • 当非utf-8编码的程序需要在python3环境上运行,只需要加对应的编码声明就行了,因为python3解释器本身就会将字符串以unicode运行。

  • 假如有一个文件存储时用的gbk编码,还限制只让用python2解释器,解决乱码问题的方法:程序头加gbk编码声明,字符串定义前加“u”。