彻底搞懂Python字符编码


Posted in Python onJanuary 23, 2018

不论你是有着多年经验的 Python 老司机还是刚入门 Python 不久,你一定遇到过UnicodeEncodeError、UnicodeDecodeError 错误,每当遇到错误我们就拿着 encode、decode 函数翻来覆去的转换,有时试着试着问题就解决了,有时候怎么试都没辙,只有借用 Google 大神帮忙,但似乎很少去关心问题的本质是什么,下次遇到类似的问题重蹈覆辙,那么你有没有想过一次性彻底把 Python 字符编码给搞懂呢?

完全理解字符编码 与 Python 的渊源前,我们有必要把一些基础概念弄清楚,虽然有些概念我们每天都在接触甚至在使用它,但并不一定真正理解它。比如:字节、字符、字符集、字符码、字符编码。

字节

字节(Byte)是计算机中数据存储的基本单元,一字节等于一个8位的比特,计算机中的所有数据,不论是保存在磁盘文件上的还是网络上传输的数据(文字、图片、视频、音频文件)都是由字节组成的。

字符

你正在阅读的这篇文章就是由很多个字符(Character)构成的,字符一个信息单位,它是各种文字和符号的统称,比如一个英文字母是一个字符,一个汉字是一个字符,一个标点符号也是一个字符。

字符集

字符集(Character Set)就是某个范围内字符的集合,不同的字符集规定了字符的个数,比如 ASCII 字符集总共有128个字符,包含了英文字母、阿拉伯数字、标点符号和控制符。而 GB2312 字符集定义了7445个字符,包含了绝大部分汉字字符。

字符码

字符码(Code Point)指的是字符集中每个字符的数字编号,例如 ASCII 字符集用 0-127 连续的128个数字分别表示128个字符,例如 "A" 的字符码编号就是65。

字符编码

字符编码(Character Encoding)是将字符集中的字符码映射为字节流的一种具体实现方案,常见的字符编码有 ASCII 编码、UTF-8 编码、GBK 编码等。某种意义上来说,字符集与字符编码有种对应关系,例如 ASCII 字符集对应 有 ASCII 编码。ASCII 字符编码规定使用单字节中低位的7个比特去编码所有的字符。例如"A" 的编号是65,用单字节表示就是0×41,因此写入存储设备的时候就是b'01000001'。

编码、解码

编码的过程是将字符转换成字节流,解码的过程是将字节流解析为字符。

理解了这些基本的术语概念后,我们就可以开始讨论计算机的字符编码的演进过程了。

从 ASCII 码说起

说到字符编码,要从计算机的诞生开始讲起,计算机发明于美国,在英语世界里,常用字符非常有限,26个字母(大小写)、10个数字、标点符号、控制符,这些字符在计算机中用一个字节的存储空间来表示绰绰有余,因为一个字节相当于8个比特位,8个比特位可以表示256个符号。于是美国国家标准协会ANSI制定了一套字符编码的标准叫 ASCII(American Standard Code for Information Interchange),每个字符都对应唯一的一个数字,比如字符 "A" 对应数字是65,"B" 对应 66,以此类推。最早 ASCII 只定义了128个字符编码,包括96个文字和32个控制符号,一共128个字符只需要一个字节的7位就能表示所有的字符,因此 ASCII 只使用了一个字节的后7位,剩下最高位1比特被用作一些通讯系统的奇偶校验。下图就是 ASCII 码字符编码的十进制、二进制和字符的对应关系表

彻底搞懂Python字符编码 

扩展的 ASCII:EASCII(ISO/8859-1)

然而计算机慢慢地普及到其他西欧地区时,发现还有很多西欧字符是 ASCII 字符集中没有的,显然 ASCII 已经没法满足人们的需求了,好在 ASCII 字符只用了字节的7位 0×00~0x7F 共128个字符,于是他们在 ASCII 的基础上把原来的7位扩充到8位,把0×80-0xFF这后面的128个数字利用起来,叫 EASCII ,它完全兼容ASCII,扩展出来的符号包括表格符号、计算符号、希腊字母和特殊的拉丁符号。然而 EASCII 时代是一个混乱的时代,各个厂家都有自己的想法,大家没有统一标准,他们各自把最高位按照自己的标准实现了自己的一套字符编码标准,比较著名的就有 CP437 , CP437 是 始祖IBM PC、MS-DOS使用的字符编码,如下图:

彻底搞懂Python字符编码 

众多的 ASCII 扩充字符集之间互不兼容,这样导致人们无法正常交流,例如200在CP437字符集表示的字符是 È ,在 ISO/8859-1 字符集里面显示的就是 ?,于是国际标准化组织(ISO)及国际电工委员会(IEC)联合制定的一系列8位字符集的标准 ISO/8859-1(Latin-1) ,它继承了 CP437 字符编码的128-159之间的字符,所以它是从160开始定义的,ISO-8859-1在 CP437 的基础上重新定义了 160~255之间的字符。

彻底搞懂Python字符编码 

多字节字符编码 GBK

ASCII 字符编码是单字节编码,计算机进入中国后面临的一个问题是如何处理汉字,对于拉丁语系国家来说通过扩展最高位,单字节表示所有的字符已经绰绰有余,但是对于亚洲国家来说一个字节就显得捉襟见肘了。于是中国人自己弄了一套叫 GB2312 的双字节字符编码,又称GB0,1981 由中国国家标准总局发布。GB2312 编码共收录了6763个汉字,同时他还兼容 ASCII,GB 2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率,不过 GB2312 还是不能100%满足中国汉字的需求,对一些罕见的字和繁体字 GB2312 没法处理,后来就在GB2312的基础上创建了一种叫 GBK 的编码,GBK 不仅收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。同样 GBK 也是兼容 ASCII 编码的,对于英文字符用1个字节来表示,汉字用两个字节来标识。

Unicode 的问世

GBK仅仅只是解决了我们自己的问题,但是计算机不止是美国人和中国人用啊,还有欧洲、亚洲其他国家的文字诸如日文、韩文全世界各地的文字加起来估计也有好几十万,这已经大大超出了ASCII 码甚至GBK 所能表示的范围了,虽然各个国家可以制定自己的编码方案,但是数据在不同国家传输就会出现各种各样的乱码问题。如果只用一种字符编码就能表示地球甚至火星上任何一个字符时,问题就迎刃而解了。是它,是它,就是它,我们的小英雄,统一联盟国际组织提出了Unicode 编码,Unicode 的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS。它为世界上每一种语言的每一个字符定义了一个唯一的字符码,Unicode 标准使用十六进制数字表示,数字前面加上前缀 U+,比如字母『A』的Unicode编码是 U+0041,汉字『中』的Unicode 编码是U+4E2D

Unicode有两种格式:UCS-2和UCS-4。UCS-2就是用两个字节编码,一共16个比特位,这样理论上最多可以表示65536个字符,不过要表示全世界所有的字符显示65536个数字还远远不过,因为光汉字就有近10万个,因此Unicode4.0规范定义了一组附加的字符编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)。理论上完全可以涵盖一切语言所用的符号。

Unicode 的局限

但是 Unicode 有一定的局限性,一个 Unicode 字符在网络上传输或者最终存储起来的时候,并不见得每个字符都需要两个字节,比如字符“A“,用一个字节就可以表示的字符,偏偏还要用两个字节,显然太浪费空间了。

第二问题是,一个 Unicode 字符保存到计算机里面时就是一串01数字,那么计算机怎么知道一个2字节的Unicode字符是表示一个2字节的字符呢,例如“汉”字的 Unicode 编码是 U+6C49,我可以用4个ascii数字来传输、保存这个字符;也可以用utf-8编码的3个连续的字节E6 B1 89来表示它。关键在于通信双方都要认可。因此Unicode编码有不同的实现方式,比如:UTF-8、UTF-16等等。Unicode就像英语一样,做为国与国之间交流世界通用的标准,每个国家有自己的语言,他们把标准的英文文档翻译成自己国家的文字,这是实现方式,就像utf-8。

具体实现:UTF-8

UTF-8(Unicode Transformation Format)作为 Unicode 的一种实现方式,广泛应用于互联网,它是一种变长的字符编码,可以根据具体情况用1-4个字节来表示一个字符。比如英文字符这些原本就可以用 ASCII 码表示的字符用UTF-8表示时就只需要一个字节的空间,和 ASCII 是一样的。对于多字节(n个字节)的字符,第一个字节的前n为都设为1,第n+1位设为0,后面字节的前两位都设为10。剩下的二进制位全部用该字符的unicode码填充。

彻底搞懂Python字符编码 

以『好』为例,『好』对应的 Unicode 是597D,对应的区间是 0000 0800--0000 FFFF,因此它用 UTF-8 表示时需要用3个字节来存储,597D用二进制表示是: 0101100101111101,填充到 1110xxxx 10xxxxxx 10xxxxxx 得到 11100101 10100101 10111101,转换成16进制是 e5a5bd,因此『好』的 Unicode 码 U+597D 对应的 UTF-8 编码是 "E5A5BD"。你可以用 Python 代码来验证:

>>> a = u"好" 
>>> a 
u'\u597d' 
>>> b = a.encode('utf-8') 
>>> len(b) 
3 
>>> b 
'\xe5\xa5\xbd'

现在总算把理论说完了。再来说说 Python 中的编码问题。Python 的诞生时间比 Unicode 要早很多,Python2 的默认编码是ASCII,正因为如此,才导致很多的编码问题。

>>> import sys 
>>> sys.getdefaultencoding() 
'ascii'

所以在 Python2 中,源代码文件必须显示地指定编码类型,否则但凡代码中出现有中文就会报语法错误

# coding=utf-8 
或者是: 
# -*- coding: utf-8 -*-

Python2 字符类型

在 python2 中和字符串相关的数据类型有 str 和 unicode 两种类型,它们继承自 basestring,而 str 类型的字符串的编码格式可以是 ascii、utf-8、gbk等任何一种类型。

彻底搞懂Python字符编码 

对于汉字『好』,用 str 表示时,它对应的 utf-8 编码 是'\xe5\xa5\xbd',对应的 gbk 编码是 '\xba\xc3',而用 unicode 表示时,他对应的符号就是u'\u597d',与u"好" 是等同的。

str 与 unicode 的转换

在 Python 中 str 和 unicode 之间是如何转换的呢?这两种类型的字符串之间的转换就是靠decode 和 encode 这两个函数。encode 负责将unicode 编码成指定的字符编码,用于存储到磁盘或传输到网络中。而 decode 方法是根据指定的编码方式解码后在应用程序中使用。

#从unicode转换到str用 encode 
>>> b = u'好' 
>>> c = b.encode('utf-8') 
>>> type(c) 
<type 'str'> 
>>> c 
'\xe5\xa5\xbd' 
#从str类型转换到unicode用decode 
>>> d = c.decode('utf-8') 
>>> type(d) 
<type 'unicode'> 
>>> d 
u'\u597d'

UnicodeXXXError 错误的原因

在字符编码转换操作时,遇到最多的问题就是 UnicodeEncodeError 和 UnicodeDecodeError 错误了,这些错误的根本原因在于 Python2 默认是使用 ascii 编码进行 decode 和 encode 操作,例如:

case 1

>>> s = '你好' 
>>> s.decode() 
Traceback (most recent call last): 
 File "<stdin>", line 1, in <module> 
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

当把 s 转换成 unicode 类型的字符串时,decode 方法默认使用 ascii 编码进行解码,而 ascii 字符集中根本就没有中文字符『你好』,所以就出现了 UnicodeDecodeError,正确的方式是显示地指定 UTF-8 字符编码。

>>> s.decode('utf-8') 
u'\u4f60\u597d'

同样地道理,对于 encode 操作,把 unicode字符串转换成 str类型的字符串时,默认也是使用 ascii 编码进行编码转换的,而 ascii 字符集找不到中文字符『你好』,于是就出现了UnicodeEncodeError 错误。

>>> a = u'你好' 
>>> a.encode() 
Traceback (most recent call last): 
 File "<stdin>", line 1, in <module> 
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

case 2

str 类型与 unicode 类型的字符串混合使用时,str 类型的字符串会隐式地将 str 转换成 unicode字符串,如果 str字符串是中文字符,那么就会出现UnicodeDecodeError 错误,因为 python2 默认会使用 ascii 编码来进行 decode 操作。

>>> s = '你好' # str类型 
>>> y = u'python' # unicode类型 
>>> s + y # 隐式转换,即 s.decode('ascii') + u 
Traceback (most recent call last): 
 File "<stdin>", line 1, in <module> 
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

正确地方式是显示地指定 UTF-8 字符编码进行解码

>>> s.decode('utf-8') +y 
u'\u4f60\u597dpython'

乱码

所有出现乱码的原因都可以归结为字符经过不同编码解码在编码的过程中使用的编码格式不一致,比如:

# encoding: utf-8 
>>> a='好' 
>>> a 
'\xe5\xa5\xbd' 
>>> b=a.decode("utf-8") 
>>> b 
u'\u597d' 
>>> c=b.encode("gbk") 
>>> c 
'\xba\xc3' 
>>> print c

utf-8编码的字符‘好'占用3个字节,解码成Unicode后,如果再用gbk来解码后,只有2个字节的长度了,最后出现了乱码的问题,因此防止乱码的最好方式就是始终坚持使用同一种编码格式对字符进行编码和解码操作。

彻底搞懂Python字符编码 

总结

以上所述是小编给大家介绍的Python字符编码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Python 相关文章推荐
Python中尝试多线程编程的一个简明例子
Apr 07 Python
Python批量重命名同一文件夹下文件的方法
May 25 Python
python 对象和json互相转换方法
Mar 22 Python
Python中py文件引用另一个py文件变量的方法
Apr 29 Python
修改默认的pip版本为对应python2.7的方法
Nov 06 Python
python批量创建指定名称的文件夹
Mar 21 Python
浅谈python图片处理Image和skimage的区别
Aug 04 Python
Django models filter筛选条件详解
Mar 16 Python
python数据库编程 Mysql实现通讯录
Mar 27 Python
深入理解Python 多线程
Jun 16 Python
Python用Jira库来操作Jira
Dec 28 Python
在 Golang 中实现 Cache::remember 方法详解
Mar 30 Python
Python实现PS滤镜的万花筒效果示例
Jan 23 #Python
python处理csv数据动态显示曲线实例代码
Jan 23 #Python
Python+matplotlib实现华丽的文本框演示代码
Jan 22 #Python
CentOS7.3编译安装Python3.6.2的方法
Jan 22 #Python
Python OpenCV实现图片上输出中文
Jan 22 #Python
python批量替换页眉页脚实例代码
Jan 22 #Python
python解析html提取数据,并生成word文档实例解析
Jan 22 #Python
You might like
PHP转换IP地址到真实地址的方法详解
2013/06/09 PHP
php字符编码转换之gb2312转为utf8
2013/10/28 PHP
ThinkPHP3.1.3版本新特性概述
2014/06/19 PHP
php批量删除操作(数据访问)
2017/05/23 PHP
javascript options属性集合操作代码
2009/12/28 Javascript
JavaScript DOM学习第八章 表单错误提示
2010/02/19 Javascript
28个JS验证函数收集
2010/03/02 Javascript
jQuery中需要注意的细节问题小结
2011/12/06 Javascript
webapp框架AngularUI的demo改造之路
2014/12/21 Javascript
JavaScript 学习笔记之语句
2015/01/14 Javascript
jQuery支持添加事件的日历特效代码分享(3种样式)
2015/08/24 Javascript
jquery实现LED广告牌旋转系统图片切换效果代码分享
2015/08/26 Javascript
JavaScript模版引擎的基本实现方法浅析
2016/02/15 Javascript
js数组去重的hash方法
2016/12/22 Javascript
JS实现随机颜色的3种方法与颜色格式的转化
2017/01/05 Javascript
javascript表达式和运算符详解
2017/02/07 Javascript
Extjs表单输入框异步校验的插件实现方法
2017/03/20 Javascript
ES6生成器用法实例分析
2017/04/10 Javascript
详解Vue iview IE浏览器不兼容报错(Iview Bable polyfill)
2019/01/07 Javascript
Vue 基于 vuedraggable 实现选中、拖拽、排序效果
2020/05/18 Javascript
Python发送以整个文件夹的内容为附件的邮件的教程
2015/05/06 Python
Pandas:Series和DataFrame删除指定轴上数据的方法
2018/11/10 Python
Python数据可视化库seaborn的使用总结
2019/01/15 Python
Python简单处理坐标排序问题示例
2019/07/11 Python
Python学习笔记之错误和异常及访问错误消息详解
2019/08/08 Python
Python3 中sorted() 函数的用法
2020/03/24 Python
python中return如何写
2020/06/18 Python
世界上最大的餐具公司:Oneida
2016/12/17 全球购物
波兰家具和室内装饰品购物网站:Vivre
2018/04/10 全球购物
初婚初育证明
2014/01/14 职场文书
军训 自我鉴定
2014/02/03 职场文书
建房协议书
2014/04/11 职场文书
归途列车观后感
2015/06/17 职场文书
新手入门Jvm-- JVM对象创建与内存分配机制
2021/06/18 Java/Android
MyBatis-Plus 批量插入数据的操作方法
2021/09/25 Java/Android
MySQL时区造成时差问题
2022/04/13 MySQL