- 浏览: 7935650 次
- 性别:
- 来自: 广州
文章分类
- 全部博客 (2425)
- 软件工程 (75)
- JAVA相关 (662)
- ajax/web相关 (351)
- 数据库相关/oracle (218)
- PHP (147)
- UNIX/LINUX/FREEBSD/solaris (118)
- 音乐探讨 (1)
- 闲话 (11)
- 网络安全等 (21)
- .NET (153)
- ROR和GOG (10)
- [网站分类]4.其他技术区 (181)
- 算法等 (7)
- [随笔分类]SOA (8)
- 收藏区 (71)
- 金融证券 (4)
- [网站分类]5.企业信息化 (3)
- c&c++学习 (1)
- 读书区 (11)
- 其它 (10)
- 收藏夹 (1)
- 设计模式 (1)
- FLEX (14)
- Android (98)
- 软件工程心理学系列 (4)
- HTML5 (6)
- C/C++ (0)
- 数据结构 (0)
- 书评 (3)
- python (17)
- NOSQL (10)
- MYSQL (85)
- java之各类测试 (18)
- nodejs (1)
- JAVA (1)
- neo4j (3)
- VUE (4)
- docker相关 (1)
最新评论
-
xiaobadi:
jacky~~~~~~~~~
推荐两个不错的mybatis GUI生成工具 -
masuweng:
(转)JAVA获得机器码的实现 -
albert0707:
有些扩展名为null
java 7中可以判断文件的contenttype了 -
albert0707:
非常感谢!!!!!!!!!
java 7中可以判断文件的contenttype了 -
zhangle:
https://zhuban.me竹板共享 - 高效便捷的文档 ...
一个不错的网络白板工具
https://mp.weixin.qq.com/s/ImVH-XZk5RyjT8D7aMCmZw
注意:本文实验主要基于win7,Python2.7;以及Linux ,Python2.7。除非特殊说明,所有的命令都是在终端中交互式输入;如果没有强调平台,那么就是window上的结果。下面是一些默认的环境信息(其重要性后文会介绍)
windows
>>> import sys,locale
>>> sys.getdefaultencoding()
'ascii'
>>> locale.getdefaultlocale()
('zh_CN', 'cp936')
>>> sys.stdin.encoding
'cp936'
>>> sys.stdout.encoding
'cp936'
>>> sys.getfilesystemencoding()
'mbcs'
注意,上面CP936是GBK的别名,在https://docs.python.org/2/library/codecs.html#standard-encodings 可以查看。
Linux
>>> import sys,locale
>>> sys.getdefaultencoding()
'ascii'
>>> locale.getdefaultlocale()
('zh_CN', 'UTF-8')
>>> sys.stdin.encoding
'UTF-8'
>>> sys.stdout.encoding
'UTF-8'
>>> sys.getfilesystemencoding()
'UTF-8'
从字符编码说起
首先来说一说gbk gb2312 unicode utf-8这些术语,这些术语与语言无关。
计算机的世界只有0和1,因此任何字符(也就是实际的文字符号)也是由01串组成。计算机为了运算方便,都是8个bit组成一个字节(Byte),字符表达的最小单位就是字节,即一个字符占用一个或者多个字节。字符编码(character encoding)就是字集码,编码就是将字符集中的字符映射为一个唯一二进制的过程。
计算机发源于美国,使用的是英文字母(字符),所有26个字母的大小写加上数字0到10,加上符号和控制字符,总数也不多,用一个字节(8个bit)就能表示所有的字符,这就是ANSI的“Ascii”编码(American Standard Code for Information Interchange,美国信息互换标准代码)。比如,小写字母‘a’的ascii 码是01100001,换算成十进制就是97,十六进制就是0x61。计算机中,一般都是用十六进制来描述字符编码。
但是当计算机传到中国的时候,ASCII编码就行不通了,汉字这么多,一个字节肯定表示不下啊,于是有了GB 2312(中国国家标准简体中文字符集)。GB2312使用两个字节来对一个字符进行编码,其中前面的一个字节(称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,GB2312能表示几千个汉字,而且与asill吗也是兼容的。
但后来发现,GB2312还是不够用,于是进行扩展,产生了GBK(即汉字内码扩展规范), GBK同Gb2312一样,两个字节表示一个字符,但区别在于,放宽了对低字节的要求,因此能表示的范围扩大到了20000多。后来,为了容纳少数名族,以及其他汉字国家的文字,出现了GB13080。GB13080是兼容GBK与GB2312的,能容纳更多的字符,与GBK与GB2312不同的是,GB18030采用单字节、双字节和四字节三种方式对字符编码
因此,就我们关心的汉字而言,三种编码方式的表示范围是:
GB18030 》 GBK 》 GB2312
即GBK是GB2312的超集,GB1803又是GBK的超集。后面也会看到,一个汉字可以用GBK表示,但不一定能被GB2312所表示
当然,世界上还有更多的语言与文字,每种文字都有自己的一套编码规则,这样一旦跨国就会出现乱码,亟待一个全球统一的解决办法。这个时候ISO(国际标准化组织)出马了,发明了”Universal Multiple-Octet Coded Character Set”,简称 UCS, 俗称 “unicode”。目标很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号 的编码!
unicode每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。unicode编码一定以u开头。
但是,unicode只是一个编码规范,是所有字符对应二进制的集合,而不是具体的编码规则。或者说,unicode是表现形式,而不是存储形式,就是说没用定义每个字符是如何以二进制的形式存储的。这个就跟GBK这些不一样,GBK是表里如下,表现形式即存储形式。
比如汉字“严”的unicode编码是u4e25,对应的二进制是1001110 00100101,但是当其经过网络传输或者文件存储时,是没法知道怎么解析这些二进制的,容易和其他字节混在一起。那么怎么存储unicode呢,于是出现了UTF(UCS Transfer Format),这个是具体的编码规则,即UTF的表现形式与存储格式是一样的。
因此,可以说,GBK和UTF-8是同一个层面的东西,跟unicode是另一个层面的东西,unicode飘在空中,如果要落地,需要转换成utf-8或者GBK。只不过,转换成Utf-8,大家都能懂,更懂用,而转换成GBK,只有中国人才看得懂
UTF也有不同的实现,如UTF-8, UTF-16, 这里以UTF-8为例进行讲解(下面一小节引用了阮一峰的文章)。
unicode与utf-8
UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。UTF-8的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
下表总结了编码规则,字母x表示可用编码的位。
Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
----------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
以汉字“严”为例,演示如何实现UTF-8编码。
已知“严”的unicode是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),因此“严”的UTF-8编码需要三个字节,即格式是“1110xxxx 10xxxxxx 10xxxxxx”。然后,从“严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,“严”的UTF-8编码是“11100100 10111000 10100101”,转换成十六进制就是E4B8A5。
当编解码遇上Python2.x
下面使用Python语言来验证上面的理论。在这一章节中,当提到unicode,一般是指unicode type,即Python中的类型;也会提到unicode编码、unicode函数,请大家注意区别。
另外,对于编码,也有两种意思。第一个是名字,指的是字符的二进制表示,如unicode编码、gbk编码。第二个是动词,指的是从字符到二进制的映射过程。不过后文中,编码作为动词,狭义理解为从unicode类型转换成str类型的过程,解码则是相反的过程。另外强调的是,unicode类型一定是unicode编码,而str类型可能是gbk、ascii或者utf-8编码。
unicode 与 str 区别
在python2.7中,有两种“字符串”类型,分别是str 与 unicode,他们有同一个基类basestring。str是plain string,其实应该称之为字节串,因为是每一个字节换一个单位长度。而unicode就是unicode string,这才是真正的字符串,一个字符(可能多个字节)算一个单位长度。
python2.7中,unicode类型需要在文本之间加u表示。
>>> us = u'严'
>>> print type(us), len(us)
<type 'unicode'> 1
>>> s = '严'
>>> print type(s), len(s)
<type 'str'> 2
>>>
从上可以看到,第一,us、s的类型是不一样的;其二,同一个汉字,不同的类型其长度也是不一样的,对于unicode类型的实例,其长度一定是字符的个数,而对于str类型的实例,其长度是字符对应的字节数目。这里强调一下,s(s = ‘严’)的长度在不同的环境下是不一样的!后文会解释
__str__ __repr__的区别
这是python中两个magic method,很容易让新手迷糊,因为很多时候,二者的实现是一样的,但是这两个函数是用在不同的地方
_str__, 主要是用于展示,str(obj)或者print obj的时候调用,返回值一定是一个str 对象
__repr__, 是被repr(obj), 或者在终端直接打obj的时候调用
>>> us = u'严'
>>> us
u'\u4e25'
>>> print us
严
可以看到,不使用print返回的是一个更能反映对象本质的结果,即us是一个unicode对象(最前面的u表示,以及unicode编码是用的u),且“严”的unicode编码确实是4E25。而print调用可us.__str__,等价于print str(us),使得结果对用户更友好。那么unicode.__str__是怎么转换成str的呢,答案会在后面揭晓
unicode str utf-8关系
前面已经提到,unicode只是编码规范(只是字符与二进制的映射集合),而utf-8是具体的编码规则(不仅包含字符与二进制的映射集合,而且映射后的二进制是可以用于存储和传输的),即utf-8负责把unicode转换成可存储和传输的二进制字符串即str类型,我们称这个转换过程为编码。而从str类型到unicode类型的过程,我们称之为解码。
Python中使用decode()和encode()来进行解码和编码,以unicode类型作为中间类型。如下图所示
decode encode
str ---------> unicode --------->str
即str类型调用decode方法转换成unicode类型,unicode类型调用encode方法转换成str类型。for example
>>> us = u'严'
>>> ss = us.encode('utf-8')
>>> ss
'\xe4\xb8\xa5'
>>> type(ss)
<type 'str'>
>>> ss.decode('utf-8') == us
True
从上可以看出encode与decode两个函数的作用,也可以看出’严’的utf8编码是E4B8A5。
就是说我们使用unicode.encode将unicode类型转换成了str类型,在上面也提到unicode.__str__也是将unicode类型转换成str类型。二者有什么却比呢
unicode.encode 与 unicode.__str__的区别
首先看看文档
str.encode([encoding[, errors]])
Return an encoded version of the string. Default encoding is the current default string encoding.
object.__str__(self)
Called by the str() built-in function and by the print statement to compute the “informal” string representation of an object.
注意:str.encode 这里的str是basestring,是str类型与unicode类型的基类
可以看到encode方法是有可选的参数:encoding 和 errors,在上面的例子中encoding即为utf-8;而__str__是没有参数的,我们可以猜想,对于unicode类型,__str__函数一定也是使用了某种encoding来对unicode进行编码。
首先不禁要问,如果encode方法没有带入参数,是什么样子的:
>>> us.encode()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u4e25' in position 0: ordinal not in range(128)
不难看出,默认使用的就是ascii码来对unicode就行编码,为什么是ascii码,其实就是系统默认编码(sys.getdefaultencoding的返回值)。ascii码显然无法表示汉字,于是抛出了异常。而使用utf-8编码的时候,由于utf能够表示这个汉字,所以没报错。
如果直接打印ss(us.encode(‘utf-8’)的返回值)会怎么样
>>> print ss
涓
结果略有些奇怪,us.__str__(即直接打印us)的结果不一样,那么试试encoding = gbk呢?
>>> print us.encode('gbk')
严
U got it! 事实上也是如此,python会采用终端默认的编码(用locale.getdefaultlocale()查看,windows是为gbk)将unicode编码成str类型。
在Linux(终端编码为utf-8),结果如下:
>>> us= u'严'
>>> print us.encode('utf-8')
严
>>> print us.encode('gbk')
▒▒
>>> print us
严
>>>
注意上面的乱码!
unicode gbk之间的转换
在上上小节,介绍了unicode可以通过utf-8编码(encoding = utf-8),转换成utf-8表示的str,在上一节也可以看出unicode也可以通过gbk编码(encoding=gbk),转换成gbk表示的str。这里有点晕,留作第一个问题,后面解释
unicode与utf8之间的相互转换可以计算得知,但unicode与gbk之间的相互转换没有计算公式,就只能靠查表了,就是说有一张映射表,有某一个汉字对应的unicode表示与gbk表示的映射关系
>> us = u'严'
>>> us
u'\u4e25'
>>> us.encode('gbk')
'\xd1\xcf'
>>> us.encode('gb2312')
'\xd1\xcf'
>>> us.encode('gb18030')
'\xd1\xcf'
>>> s = '严'
>>> s
'\xd1\xcf'
>>>
从上不难看出,严的unicdoe编码是4e25,GBK编码是d1cf,因此us通过gbk编码就是d1cf。同样也能看到,GB18030,GBK,GB2312是兼容的
为什么print us.encode(‘utf-8’)打印出“涓”
ss = us.encode(‘utf-8’), ss是一个str类型,直接打印结果有点奇怪,一个“涓”字,那一个str类型的“涓”是哪些二进制组成的呢
>>> s = '涓'
>>> s
'\xe4\xb8'
可以看到,str类型的“涓”,其二进制是E4B8,跟’严’的utf8编码(E4B8A5)相差了一个A5,那么就是因为A5显示不出来,验证如下:
>>> print '--%s--' % ss
--涓?-
因此,只是碰巧显示了“涓”而已,事实上ss跟“”涓“”毫无关系
回答第一个问题:str类型到底是什么
在上上小节,提到了utf-8编码的str,与gbk编码的str,感觉有点绕。我们知道,一个汉字‘严’,可存储的编码格式可以是gbk(’xd1xcf’),也可以是utf-8(’xe4xb8xa5’),那么当我们在终端敲入这个汉字的时候,是哪一种格式呢?取决于终端默认编码。
windows上(默认终端编码为gbk):
>>> s = '严'
>>> s
'\xd1\xcf'
Linux上(默认终端编码为utf-8):
>>> a = '严'
>>> a
'\xe4\xb8\xa5'
同样一个汉字,同样都是Python中的str类型,在不同的编码格式下,其二进制是不一样的。因此,其长度也是不一样的,对于str类型,其长度是对应的字节长度。
也能看出gbk编码的字节长度一般小于utf-8,这也是gbk继续存在的一个原因。
这里,要强调一下,unicode的二进制形式是与终端的编码格式无关的!这个也不难理解。
unicode函数
str类型到unicode类型的转换,出了上面提到的str.decode,还有一个unicode函数。两个函数的签名为:
unicode(object[, encoding[, errors]])
Return the Unicode string version of object using one of the following modes:
str.decode([encoding[, errors]])
Decodes the string using the codec registered for encoding. encoding defaults to the default string encoding.
二者参数相同,事实上二者是等价的,encoding的默认值也是一样的,都是sys.getdefaultencoding()的结果。for example:
>>> s = '严'
>>> newuse = unicode(s)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd1 in position 0: ordinal not in range(128)
>>> newuse = unicode(s, 'utf-8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf8' codec can't decode byte 0xd1 in position 0: invalid continuation byte
>>> newuse = unicode(s, 'gbk')
>>> newuse
u'\u4e25'
第一个UnicodeDecodeError,就是因为系统默认的编码是asill吗;第二个UnicodeDecodeError,是因为,s(str类型的实例)的编码取决于终端默认编码(即windows下的gbk),为了能打印出来,也就必须用gbk编码来表示这个str,因此只能查询gbk与unicode的映射表将s转换成unicode类型。
为啥调用sys.setdefaultencoding
在诸多Python代码中,都会看到这么一段:
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
不难猜想,setdefaultencoding跟getdefaultencoding是配对的,为啥要将系统的默认编码设置成utf-8,其实就是解决str到unicode的转换问题。
上一小节已经提到过,使用unicode函数将str类型转换成unicode类型时,要考虑两个因素:第一,str本身是什么编码的;第二,如果没有传入encoding参数,默认使用sys.getdefaultencoding。encoding参数必须与str本身的编码对应,否则就是UnicodeDecodeError。
写python代码的程序都知道,我们要在py文件第一行写上:
# -*- coding: utf-8 -*-
这句话的作用在于,告诉编辑器,该文件里面的所有str都采用utf-8编码,且存储文件的时候也是使用utf-8格式。
然后文件中就会使用下面的这种代码。
s='中文'
us=unicode(s)
使用unicode强制转换的时候,都不习惯带参数,为了保证encoding参数必须与str本身的编码一致,所以使用setdefaultencoding将系统默认编码设置为utf-8
乱码与UnicodeError
下面介绍几种常见的乱码与异常UnicodeError, 大多数乱码或者异常的原因在前面已经讲过了,同时,对于一些乱码,也试图给出可行的解决办法。
UnicodeError包括UnicodeDecodeError 与UnicodeEncodeError ,前者是decode也就是str转unicode的时候出了异常,后者则是encode也就是unicode转str的时候出了异常。
对于一个str,直接打印
例子就是上面反复提到的例子
>>> ss = us.encode('utf-8')
>>> print ss
涓
如果一个str类型来自网络或者文件读取,最好先按照对端encode的方式先decode成unicode,然后再输出(输出的时候会自动转换成期望终端支持的编码格式的str)
编码范围无法包括的汉字
直接上例子
>>> newus = u'囍'
>>> newus
u'\u56cd'
>>> newus.encode('gbk')
'\x87\xd6'
>>> newus.encode('gb2312')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'gb2312' codec can't encode character u'\u56cd' in position 0: illegal multibyte sequence
>>>
可以看到,‘囍’字可以被gbk编码,但是不能被gb2312编码。
str转unicode的时候
在上面讲unicode函数的时候已经举过例子,会爆出UnicodeDecodeError 异常。
这个错误比较的原因,更多来自str到unicode的默认转换,比如一个str与一个unicode相加的时候:
>>> a = '严'
>>> b = u'严'
>>> c = a + b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd1 in position 0: ordinal not in range(128)
unicode 与 str相加,str会转换为unicode,使用默认的unicode(strobj, encoding = sys.getdefaultencoding())
看起来向unicode编码的字符串
某些情况下,我们打印出一个str类型,看到结果是’\u4e25’, 或者’u4e25’,对于这个字符串,是不是很眼熟,不错, ‘严‘的unicode编码就是u’u4e25’。仔细一看,只是在引号前面多了一个u(表示是一个unicode类型)。那么当我们看到一个’u4e25’的时候,怎么知道对应的汉字是什么?对于已知的这种格式的str,自然可以手动加一个u,然后在终端输出,但是如果是一个变量,需要自动转换成unicode呢,这个时候就可以使用python-specific-encodings中的unicode_escape
>>> s = '\u4e25'
>>> s
'\\u4e25'
>>> us = s.decode('unicode_escape')
>>> us
u'\u4e25'
十六进制格式的字符串
有时候,也会看到类似这样的str,’\xd1\xcf’, 看起来也很熟悉,跟汉字“严”的gbk编码’xd1xcf’很像,区别在于前者多了一个‘’, 这样就无法解释成一个十六进制了。解决办法是python-specific-encodings中的string_escape
>>> s='\\xd1\\xcf'
>>> s
'\\xd1\\xcf'
>>> print s
\xd1\xcf
>>> news = s.decode('string_escape')
>>> news
'\xd1\xcf'
>>> print news
严
给读者的一个问题
在这里留下一个问题:
u'严' == '严'
返回值是True 还是 False呢?当然这里故意省去了上下文环境,不过明确的说,在不同的编码环境下,答案是不一样的,原因都在上文中!
总结与建议
不管怎么样解释,python2.x中的字符编码还是一件让人头疼的事情,即使搞懂了,之后遇到了也可能忘记。对于这个问题,诸多建议如下:
第一:使用python3,就不用再纠结str于unicode了;但是这个很难开发者说了算;
第二:不要使用中文,注释什么的都用英文;理想很丰满,现实很难,只是导致大量的拼音;
第三:对于中文字符串,不要用str表示,而是用unicode表示;现实中也不好实施,大家都不愿意多写一个u
第四:只在传输,或者持久化的时候对unicode进行encode,相反的过程时decode
第五:对于网络接口,约定好编解码格式,强烈建议使用utf-8
第六:看到UnicodeXXXError不要慌,如果XXX是Encode,那么一定是unicode转str的时候出了问题;如果是Decode,一定是str转unicode的时候出了问题。
注意:本文实验主要基于win7,Python2.7;以及Linux ,Python2.7。除非特殊说明,所有的命令都是在终端中交互式输入;如果没有强调平台,那么就是window上的结果。下面是一些默认的环境信息(其重要性后文会介绍)
windows
>>> import sys,locale
>>> sys.getdefaultencoding()
'ascii'
>>> locale.getdefaultlocale()
('zh_CN', 'cp936')
>>> sys.stdin.encoding
'cp936'
>>> sys.stdout.encoding
'cp936'
>>> sys.getfilesystemencoding()
'mbcs'
注意,上面CP936是GBK的别名,在https://docs.python.org/2/library/codecs.html#standard-encodings 可以查看。
Linux
>>> import sys,locale
>>> sys.getdefaultencoding()
'ascii'
>>> locale.getdefaultlocale()
('zh_CN', 'UTF-8')
>>> sys.stdin.encoding
'UTF-8'
>>> sys.stdout.encoding
'UTF-8'
>>> sys.getfilesystemencoding()
'UTF-8'
从字符编码说起
首先来说一说gbk gb2312 unicode utf-8这些术语,这些术语与语言无关。
计算机的世界只有0和1,因此任何字符(也就是实际的文字符号)也是由01串组成。计算机为了运算方便,都是8个bit组成一个字节(Byte),字符表达的最小单位就是字节,即一个字符占用一个或者多个字节。字符编码(character encoding)就是字集码,编码就是将字符集中的字符映射为一个唯一二进制的过程。
计算机发源于美国,使用的是英文字母(字符),所有26个字母的大小写加上数字0到10,加上符号和控制字符,总数也不多,用一个字节(8个bit)就能表示所有的字符,这就是ANSI的“Ascii”编码(American Standard Code for Information Interchange,美国信息互换标准代码)。比如,小写字母‘a’的ascii 码是01100001,换算成十进制就是97,十六进制就是0x61。计算机中,一般都是用十六进制来描述字符编码。
但是当计算机传到中国的时候,ASCII编码就行不通了,汉字这么多,一个字节肯定表示不下啊,于是有了GB 2312(中国国家标准简体中文字符集)。GB2312使用两个字节来对一个字符进行编码,其中前面的一个字节(称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,GB2312能表示几千个汉字,而且与asill吗也是兼容的。
但后来发现,GB2312还是不够用,于是进行扩展,产生了GBK(即汉字内码扩展规范), GBK同Gb2312一样,两个字节表示一个字符,但区别在于,放宽了对低字节的要求,因此能表示的范围扩大到了20000多。后来,为了容纳少数名族,以及其他汉字国家的文字,出现了GB13080。GB13080是兼容GBK与GB2312的,能容纳更多的字符,与GBK与GB2312不同的是,GB18030采用单字节、双字节和四字节三种方式对字符编码
因此,就我们关心的汉字而言,三种编码方式的表示范围是:
GB18030 》 GBK 》 GB2312
即GBK是GB2312的超集,GB1803又是GBK的超集。后面也会看到,一个汉字可以用GBK表示,但不一定能被GB2312所表示
当然,世界上还有更多的语言与文字,每种文字都有自己的一套编码规则,这样一旦跨国就会出现乱码,亟待一个全球统一的解决办法。这个时候ISO(国际标准化组织)出马了,发明了”Universal Multiple-Octet Coded Character Set”,简称 UCS, 俗称 “unicode”。目标很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号 的编码!
unicode每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。unicode编码一定以u开头。
但是,unicode只是一个编码规范,是所有字符对应二进制的集合,而不是具体的编码规则。或者说,unicode是表现形式,而不是存储形式,就是说没用定义每个字符是如何以二进制的形式存储的。这个就跟GBK这些不一样,GBK是表里如下,表现形式即存储形式。
比如汉字“严”的unicode编码是u4e25,对应的二进制是1001110 00100101,但是当其经过网络传输或者文件存储时,是没法知道怎么解析这些二进制的,容易和其他字节混在一起。那么怎么存储unicode呢,于是出现了UTF(UCS Transfer Format),这个是具体的编码规则,即UTF的表现形式与存储格式是一样的。
因此,可以说,GBK和UTF-8是同一个层面的东西,跟unicode是另一个层面的东西,unicode飘在空中,如果要落地,需要转换成utf-8或者GBK。只不过,转换成Utf-8,大家都能懂,更懂用,而转换成GBK,只有中国人才看得懂
UTF也有不同的实现,如UTF-8, UTF-16, 这里以UTF-8为例进行讲解(下面一小节引用了阮一峰的文章)。
unicode与utf-8
UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。UTF-8的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
下表总结了编码规则,字母x表示可用编码的位。
Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
----------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
以汉字“严”为例,演示如何实现UTF-8编码。
已知“严”的unicode是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),因此“严”的UTF-8编码需要三个字节,即格式是“1110xxxx 10xxxxxx 10xxxxxx”。然后,从“严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,“严”的UTF-8编码是“11100100 10111000 10100101”,转换成十六进制就是E4B8A5。
当编解码遇上Python2.x
下面使用Python语言来验证上面的理论。在这一章节中,当提到unicode,一般是指unicode type,即Python中的类型;也会提到unicode编码、unicode函数,请大家注意区别。
另外,对于编码,也有两种意思。第一个是名字,指的是字符的二进制表示,如unicode编码、gbk编码。第二个是动词,指的是从字符到二进制的映射过程。不过后文中,编码作为动词,狭义理解为从unicode类型转换成str类型的过程,解码则是相反的过程。另外强调的是,unicode类型一定是unicode编码,而str类型可能是gbk、ascii或者utf-8编码。
unicode 与 str 区别
在python2.7中,有两种“字符串”类型,分别是str 与 unicode,他们有同一个基类basestring。str是plain string,其实应该称之为字节串,因为是每一个字节换一个单位长度。而unicode就是unicode string,这才是真正的字符串,一个字符(可能多个字节)算一个单位长度。
python2.7中,unicode类型需要在文本之间加u表示。
>>> us = u'严'
>>> print type(us), len(us)
<type 'unicode'> 1
>>> s = '严'
>>> print type(s), len(s)
<type 'str'> 2
>>>
从上可以看到,第一,us、s的类型是不一样的;其二,同一个汉字,不同的类型其长度也是不一样的,对于unicode类型的实例,其长度一定是字符的个数,而对于str类型的实例,其长度是字符对应的字节数目。这里强调一下,s(s = ‘严’)的长度在不同的环境下是不一样的!后文会解释
__str__ __repr__的区别
这是python中两个magic method,很容易让新手迷糊,因为很多时候,二者的实现是一样的,但是这两个函数是用在不同的地方
_str__, 主要是用于展示,str(obj)或者print obj的时候调用,返回值一定是一个str 对象
__repr__, 是被repr(obj), 或者在终端直接打obj的时候调用
>>> us = u'严'
>>> us
u'\u4e25'
>>> print us
严
可以看到,不使用print返回的是一个更能反映对象本质的结果,即us是一个unicode对象(最前面的u表示,以及unicode编码是用的u),且“严”的unicode编码确实是4E25。而print调用可us.__str__,等价于print str(us),使得结果对用户更友好。那么unicode.__str__是怎么转换成str的呢,答案会在后面揭晓
unicode str utf-8关系
前面已经提到,unicode只是编码规范(只是字符与二进制的映射集合),而utf-8是具体的编码规则(不仅包含字符与二进制的映射集合,而且映射后的二进制是可以用于存储和传输的),即utf-8负责把unicode转换成可存储和传输的二进制字符串即str类型,我们称这个转换过程为编码。而从str类型到unicode类型的过程,我们称之为解码。
Python中使用decode()和encode()来进行解码和编码,以unicode类型作为中间类型。如下图所示
decode encode
str ---------> unicode --------->str
即str类型调用decode方法转换成unicode类型,unicode类型调用encode方法转换成str类型。for example
>>> us = u'严'
>>> ss = us.encode('utf-8')
>>> ss
'\xe4\xb8\xa5'
>>> type(ss)
<type 'str'>
>>> ss.decode('utf-8') == us
True
从上可以看出encode与decode两个函数的作用,也可以看出’严’的utf8编码是E4B8A5。
就是说我们使用unicode.encode将unicode类型转换成了str类型,在上面也提到unicode.__str__也是将unicode类型转换成str类型。二者有什么却比呢
unicode.encode 与 unicode.__str__的区别
首先看看文档
str.encode([encoding[, errors]])
Return an encoded version of the string. Default encoding is the current default string encoding.
object.__str__(self)
Called by the str() built-in function and by the print statement to compute the “informal” string representation of an object.
注意:str.encode 这里的str是basestring,是str类型与unicode类型的基类
可以看到encode方法是有可选的参数:encoding 和 errors,在上面的例子中encoding即为utf-8;而__str__是没有参数的,我们可以猜想,对于unicode类型,__str__函数一定也是使用了某种encoding来对unicode进行编码。
首先不禁要问,如果encode方法没有带入参数,是什么样子的:
>>> us.encode()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u4e25' in position 0: ordinal not in range(128)
不难看出,默认使用的就是ascii码来对unicode就行编码,为什么是ascii码,其实就是系统默认编码(sys.getdefaultencoding的返回值)。ascii码显然无法表示汉字,于是抛出了异常。而使用utf-8编码的时候,由于utf能够表示这个汉字,所以没报错。
如果直接打印ss(us.encode(‘utf-8’)的返回值)会怎么样
>>> print ss
涓
结果略有些奇怪,us.__str__(即直接打印us)的结果不一样,那么试试encoding = gbk呢?
>>> print us.encode('gbk')
严
U got it! 事实上也是如此,python会采用终端默认的编码(用locale.getdefaultlocale()查看,windows是为gbk)将unicode编码成str类型。
在Linux(终端编码为utf-8),结果如下:
>>> us= u'严'
>>> print us.encode('utf-8')
严
>>> print us.encode('gbk')
▒▒
>>> print us
严
>>>
注意上面的乱码!
unicode gbk之间的转换
在上上小节,介绍了unicode可以通过utf-8编码(encoding = utf-8),转换成utf-8表示的str,在上一节也可以看出unicode也可以通过gbk编码(encoding=gbk),转换成gbk表示的str。这里有点晕,留作第一个问题,后面解释
unicode与utf8之间的相互转换可以计算得知,但unicode与gbk之间的相互转换没有计算公式,就只能靠查表了,就是说有一张映射表,有某一个汉字对应的unicode表示与gbk表示的映射关系
>> us = u'严'
>>> us
u'\u4e25'
>>> us.encode('gbk')
'\xd1\xcf'
>>> us.encode('gb2312')
'\xd1\xcf'
>>> us.encode('gb18030')
'\xd1\xcf'
>>> s = '严'
>>> s
'\xd1\xcf'
>>>
从上不难看出,严的unicdoe编码是4e25,GBK编码是d1cf,因此us通过gbk编码就是d1cf。同样也能看到,GB18030,GBK,GB2312是兼容的
为什么print us.encode(‘utf-8’)打印出“涓”
ss = us.encode(‘utf-8’), ss是一个str类型,直接打印结果有点奇怪,一个“涓”字,那一个str类型的“涓”是哪些二进制组成的呢
>>> s = '涓'
>>> s
'\xe4\xb8'
可以看到,str类型的“涓”,其二进制是E4B8,跟’严’的utf8编码(E4B8A5)相差了一个A5,那么就是因为A5显示不出来,验证如下:
>>> print '--%s--' % ss
--涓?-
因此,只是碰巧显示了“涓”而已,事实上ss跟“”涓“”毫无关系
回答第一个问题:str类型到底是什么
在上上小节,提到了utf-8编码的str,与gbk编码的str,感觉有点绕。我们知道,一个汉字‘严’,可存储的编码格式可以是gbk(’xd1xcf’),也可以是utf-8(’xe4xb8xa5’),那么当我们在终端敲入这个汉字的时候,是哪一种格式呢?取决于终端默认编码。
windows上(默认终端编码为gbk):
>>> s = '严'
>>> s
'\xd1\xcf'
Linux上(默认终端编码为utf-8):
>>> a = '严'
>>> a
'\xe4\xb8\xa5'
同样一个汉字,同样都是Python中的str类型,在不同的编码格式下,其二进制是不一样的。因此,其长度也是不一样的,对于str类型,其长度是对应的字节长度。
也能看出gbk编码的字节长度一般小于utf-8,这也是gbk继续存在的一个原因。
这里,要强调一下,unicode的二进制形式是与终端的编码格式无关的!这个也不难理解。
unicode函数
str类型到unicode类型的转换,出了上面提到的str.decode,还有一个unicode函数。两个函数的签名为:
unicode(object[, encoding[, errors]])
Return the Unicode string version of object using one of the following modes:
str.decode([encoding[, errors]])
Decodes the string using the codec registered for encoding. encoding defaults to the default string encoding.
二者参数相同,事实上二者是等价的,encoding的默认值也是一样的,都是sys.getdefaultencoding()的结果。for example:
>>> s = '严'
>>> newuse = unicode(s)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd1 in position 0: ordinal not in range(128)
>>> newuse = unicode(s, 'utf-8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf8' codec can't decode byte 0xd1 in position 0: invalid continuation byte
>>> newuse = unicode(s, 'gbk')
>>> newuse
u'\u4e25'
第一个UnicodeDecodeError,就是因为系统默认的编码是asill吗;第二个UnicodeDecodeError,是因为,s(str类型的实例)的编码取决于终端默认编码(即windows下的gbk),为了能打印出来,也就必须用gbk编码来表示这个str,因此只能查询gbk与unicode的映射表将s转换成unicode类型。
为啥调用sys.setdefaultencoding
在诸多Python代码中,都会看到这么一段:
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
不难猜想,setdefaultencoding跟getdefaultencoding是配对的,为啥要将系统的默认编码设置成utf-8,其实就是解决str到unicode的转换问题。
上一小节已经提到过,使用unicode函数将str类型转换成unicode类型时,要考虑两个因素:第一,str本身是什么编码的;第二,如果没有传入encoding参数,默认使用sys.getdefaultencoding。encoding参数必须与str本身的编码对应,否则就是UnicodeDecodeError。
写python代码的程序都知道,我们要在py文件第一行写上:
# -*- coding: utf-8 -*-
这句话的作用在于,告诉编辑器,该文件里面的所有str都采用utf-8编码,且存储文件的时候也是使用utf-8格式。
然后文件中就会使用下面的这种代码。
s='中文'
us=unicode(s)
使用unicode强制转换的时候,都不习惯带参数,为了保证encoding参数必须与str本身的编码一致,所以使用setdefaultencoding将系统默认编码设置为utf-8
乱码与UnicodeError
下面介绍几种常见的乱码与异常UnicodeError, 大多数乱码或者异常的原因在前面已经讲过了,同时,对于一些乱码,也试图给出可行的解决办法。
UnicodeError包括UnicodeDecodeError 与UnicodeEncodeError ,前者是decode也就是str转unicode的时候出了异常,后者则是encode也就是unicode转str的时候出了异常。
对于一个str,直接打印
例子就是上面反复提到的例子
>>> ss = us.encode('utf-8')
>>> print ss
涓
如果一个str类型来自网络或者文件读取,最好先按照对端encode的方式先decode成unicode,然后再输出(输出的时候会自动转换成期望终端支持的编码格式的str)
编码范围无法包括的汉字
直接上例子
>>> newus = u'囍'
>>> newus
u'\u56cd'
>>> newus.encode('gbk')
'\x87\xd6'
>>> newus.encode('gb2312')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'gb2312' codec can't encode character u'\u56cd' in position 0: illegal multibyte sequence
>>>
可以看到,‘囍’字可以被gbk编码,但是不能被gb2312编码。
str转unicode的时候
在上面讲unicode函数的时候已经举过例子,会爆出UnicodeDecodeError 异常。
这个错误比较的原因,更多来自str到unicode的默认转换,比如一个str与一个unicode相加的时候:
>>> a = '严'
>>> b = u'严'
>>> c = a + b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd1 in position 0: ordinal not in range(128)
unicode 与 str相加,str会转换为unicode,使用默认的unicode(strobj, encoding = sys.getdefaultencoding())
看起来向unicode编码的字符串
某些情况下,我们打印出一个str类型,看到结果是’\u4e25’, 或者’u4e25’,对于这个字符串,是不是很眼熟,不错, ‘严‘的unicode编码就是u’u4e25’。仔细一看,只是在引号前面多了一个u(表示是一个unicode类型)。那么当我们看到一个’u4e25’的时候,怎么知道对应的汉字是什么?对于已知的这种格式的str,自然可以手动加一个u,然后在终端输出,但是如果是一个变量,需要自动转换成unicode呢,这个时候就可以使用python-specific-encodings中的unicode_escape
>>> s = '\u4e25'
>>> s
'\\u4e25'
>>> us = s.decode('unicode_escape')
>>> us
u'\u4e25'
十六进制格式的字符串
有时候,也会看到类似这样的str,’\xd1\xcf’, 看起来也很熟悉,跟汉字“严”的gbk编码’xd1xcf’很像,区别在于前者多了一个‘’, 这样就无法解释成一个十六进制了。解决办法是python-specific-encodings中的string_escape
>>> s='\\xd1\\xcf'
>>> s
'\\xd1\\xcf'
>>> print s
\xd1\xcf
>>> news = s.decode('string_escape')
>>> news
'\xd1\xcf'
>>> print news
严
给读者的一个问题
在这里留下一个问题:
u'严' == '严'
返回值是True 还是 False呢?当然这里故意省去了上下文环境,不过明确的说,在不同的编码环境下,答案是不一样的,原因都在上文中!
总结与建议
不管怎么样解释,python2.x中的字符编码还是一件让人头疼的事情,即使搞懂了,之后遇到了也可能忘记。对于这个问题,诸多建议如下:
第一:使用python3,就不用再纠结str于unicode了;但是这个很难开发者说了算;
第二:不要使用中文,注释什么的都用英文;理想很丰满,现实很难,只是导致大量的拼音;
第三:对于中文字符串,不要用str表示,而是用unicode表示;现实中也不好实施,大家都不愿意多写一个u
第四:只在传输,或者持久化的时候对unicode进行encode,相反的过程时decode
第五:对于网络接口,约定好编解码格式,强烈建议使用utf-8
第六:看到UnicodeXXXError不要慌,如果XXX是Encode,那么一定是unicode转str的时候出了问题;如果是Decode,一定是str转unicode的时候出了问题。
发表评论
-
python 的requests小结
2018-05-06 18:48 1097GET 请求 >>> r = request ... -
PYTHON抓取公众号
2018-04-26 08:19 27711.基于搜狗微信搜索的微信公众号爬虫 a. 项目地址:htt ... -
KMN算法初学
2018-04-16 20:04 1827KMN算法,其实就是"人以类聚,物有群分“,可以参考 ... -
jupyter 指定默认的打开路径
2018-04-16 20:03 2396jupyter notebook是挺好用的,但是老打开默认 ... -
python 爬虫小结2
2018-04-08 19:08 6941 LXML是比beautisoup速度更快的解析,使用的是X ... -
python 爬虫小结1
2018-04-05 11:53 710python 爬虫小结1 1 正则匹配中注意的: impor ... -
python3 中jupyter开发工具的几个魔法命令
2018-03-28 20:10 8981 %run myscript/hello.py 可以执 ... -
python使用beutifulsoup来爬虫的基本套路
2018-03-26 23:19 1069使用python3,比如爬kugo的榜单: import ... -
scrapy3在python2,python3共存下的使用
2017-12-06 09:51 1036因为安装了PYTHON2,PYTHON3,之前的SCRAPY ... -
(转)两句话轻松掌握python最难知识点——元类
2017-10-15 20:42 870https://segmentfault.com/a/1190 ... -
python的深复制和浅复制
2017-10-12 22:34 568附上一篇不错的说PYTHON深浅复制的文: http://ww ... -
python中常见字符串操作小结
2017-10-07 23:11 604#!/usr/bin/env python #-*- codi ... -
python要点1
2017-08-18 22:06 528python要点 1 2.7下安装PIP https ... -
python学习小结3
2012-02-21 14:46 3832一 文件 1)open 函数 o=op ... -
python 初步学习 小结2
2012-02-16 08:57 2321一 字符串 1) 字符串的索引可以是负数,比如str= ... -
python学习小结1
2012-02-13 11:39 51031 使用idel新建立程序后,保存运行,CTRL+F5即可运行 ...
相关推荐
**Python编程基础篇** 在本Python编程系列教程的基础篇中,我们将深入学习Python这门强大且易学的编程语言。本教程旨在为初学者提供一个全面的起点,涵盖从环境配置到基本语法、数据结构、文件操作以及函数的使用。...
这篇超详细的Python入门教程旨在帮助新手在1小时内快速掌握Python的基本概念和应用。 首先,Python的适用性体现在它能够轻松地处理各种任务,如系统管理、Web开发、科学计算等。在上述示例中,对比了使用Java和...
Python数据分析三部曲中的Pandas篇主要讲解了如何在Python环境中安装和使用Pandas库进行数据处理。Pandas是Python中一个强大的数据处理库,它提供了高效的数据结构,如DataFrame和Series,使得数据清洗、分析和操作...
本篇文章将深入探讨Python3标准模块的实例学习,帮助你更好地系统地学习和熟练掌握这些模块。 首先,我们来看一下“标准模块”的概念。标准模块是Python解释器自带的一系列模块,无需额外安装即可直接使用。它们...
标题《Python读取YUV(NV12)视频文件实例》和内容描述表明这篇文章主要讨论了如何使用Python编程语言读取和处理YUV格式(特别是NV12色度子采样格式)的视频文件。YUV格式是一种常用于视频处理的颜色编码方法,它包括...
本篇文章将详细讲解如何利用PyPDF2库来合并多个PDF文件,以及相关的资源和代码示例。 首先,我们需要了解PyPDF2库的基本用法。PyPDF2提供了一个简单的API,允许我们方便地访问PDF文件的内容。要安装这个库,你可以...
标题 "一个小时入门Python.docx" 是一篇旨在帮助初学者快速掌握Python编程基础知识的文章。这篇文章以简洁明快的方式,概述了Python编程的一些核心概念,让读者能在短时间内对Python有一个初步的了解。 首先,文章...
2. **进阶篇**:深入讲解了Python的面向对象编程,如类和对象的概念、继承、封装和多态。此外,还包括了上下文管理器、装饰器、生成器等高级语言特性。 3. **标准库篇**:详细介绍了Python的标准库,如系统接口和...
在本篇内容中,我们讨论的是Python开发编码规范的一些基本准则和实践。编码规范是一种编程习惯,用以保证代码的整洁性、可读性和一致性,这样不仅方便于团队协作,也有助于代码的维护和扩展。 标题“Python 开发...
本篇文章将详细讲解Python中URL参数的编码与解码,并通过实例进行演示。 一、URL编码 URL编码是将URL中的特殊字符或非ASCII字符转换为百分号编码(%xx)的过程,其中xx是字符的ASCII值的十六进制表示。在Python中,...
本篇文章将详细探讨Python官方文档的各个部分,帮助你更好地理解和利用这些资源。 1. **library目录**:Python的标准库是其强大功能的核心,它包含了各种模块和包,覆盖了从网络通信到数据处理等众多领域。library...
Python是一种高级、通用的编程语言,以其简洁明了的语法和强大的功能而受到广大程序员的喜爱。在"Python知识点"这个主题中,我们将深入探讨Python语言的基础和进阶知识,包括但不限于变量、数据类型、控制流、函数、...
本篇教程通过Python语言来实现一个简单的购物系统,旨在介绍如何设计并开发出一个具有基本功能的电商平台模型。 #### 二、系统结构概述 该购物系统分为两大主要部分:用户端与商家端。 - **用户端**:用户可以...
本篇将详细讲解如何使用Python来实现OCR功能,以便将文档转换为可编辑的文本。 Python提供了多个库来支持OCR操作,其中最常用的有Tesseract和PyTesseract。Tesseract是Google开发的一个开源OCR引擎,而PyTesseract...
近日看到某知名培训机构的教学视频中再次谈及此问题,讲解的还是不尽人意,所以才想写这篇文字。一方面,梳理一下相关知识,另一方面,希望给其他人些许帮助。 Python2的 默认编码 是ASCII,不能识别中文字符,需要...
本篇将详细讲解DES加密算法的基本原理,以及如何通过Python代码实现这个过程。 首先,DES算法基于Feistel结构,它将一个64位的数据块分成左半部分L和右半部分R,然后通过一系列的迭代过程(16轮)进行加密。每轮...
本篇文章将详细讲解名为"hydrodata-0.1.1-py2.py3-none-any.whl"的Python库,以及与之相关的Python开发语言和库的使用。 "hydrodata-0.1.1-py2.py3-none-any.whl"是一个Python的可分发包格式,它是Python Wheel格式...
"怎样使用Python打造免杀payload_NOSEC安全讯息平台 - 白帽汇安全研究院.html"是一篇教程,可能详细介绍了如何利用Python编写免杀payload,payload通常指的是恶意代码的核心部分,负责实现特定的攻击目标。...