剑客
关注科技互联网

Python2中的中文字符编解码浅析

自动化测试过程中,输入文本、读取文件、解析网络请求、字符串断言、正则匹配这些步骤都是必不可少的。而Python是测试过程中最为常用的语言之一,很多测试团队的自动化代码和用例都是使用Python语言开发和维护的。

由于Python在最初发布时,Unicode标准还没有完成,所以一直以来Python对Unicode的支持并不完全,而ASCII编码支持的字符有限。因此在涉及到中文的自动化用例中,经常会遇到中文字符编解码的各种各样的异常。本文从文字编码的历史讲起,抛砖引玉,浅析了Python2.x版本中文字处理的原理和可能遇到的问题。

                               一、处编码发展史

ASCII

计算机被发明出来后,最开始只在美国使用。当时的计算机使用8个可以开合的晶体管来表示不同的状态,其中0~31这32种状态对应特殊的用途,例如0x10表示换行。使用32~126表示可打印的字符,即数字、字母以及其他有字面含义的英文符号。这种编码方式普及之后,大家给它起了个名字叫做 ANSI 的“ASCII”编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上所有的计算机都用同样的ASCII方案来保存英文文字。

Python2中的中文字符编解码浅析

后来,世界各国都开始使用计算机,但很多国家使用的语言不是英文,里面的字母在ASCII中不存在,为了可以在计算机上保存这些文字,127号之后的空位被用来表示这些新的字母、符号,同时还加入了画表格用到的横线、竖线、交叉线等。这些新加入的字符被统称为“扩展字符集”。

GB2312

随着新加入的字符不断增多,128到255的编码被用完了。这时,中国人开始用计算机,8bit的所有组合都被占用,已经没有多余的字符来表示汉字(其实有也白扯,常用汉字有6000多个,就算扩展字符集全用上也仅仅覆盖不到2%)。但是,劳动人民的智慧是无穷的,设计中文字符集的人把那些127号之后的奇异符号们直接取消掉,规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都重新编了两个字节长的编码,这就是常说的“全角”字符,而原来在127号以下的那些就叫“半角”字符。这种编码方案就是大家熟知的“GB2312”。 GB2312 是对ASCII的中文扩展。

GBK/GB18030

但是,中国的汉字太多了,我们很快就发现有许多生僻字在GB2312中没有(比如某国家领导人的名字),于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。扩展之后的编码方案被称为 GBK 标准,GBK包括了GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。

后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK扩成了 GB18030。

Unicode

因为当时各个国家都和中国一样搞出一套自己的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码,连大陆和台湾这样只相隔了150海里,使用着同一种语言的兄弟地区,也分别采用了不同的 DBCS 编码方案(台湾用BIG5,俗称大五码)。

ISO (国际标准化组织)组织决定着手解决这个问题。他们采用的方法很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号的编码!他们叫它“Universal Multiple-Octet Coded Character Set”,简称 UCS, 俗称“Unicode”。

Unicode开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了。于是 ISO 就直接规定必须用两个字节,也就是16位来统一表示所有的字符,对于ASCII里的那些“半角”字符,Unicode保持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于“半角”英文符号只需要用到低8位,所以其高8位永远是0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。

Unicode同样也不完美

问题一:

如何才能区别Unicode和ASCII?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?

问题二:

我们已经知道,英文字母只用一个字节表示就够了,如果Unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储空间来说是极大的浪费,文本文件的大小会因此大出二三倍,这是难以接受的。

UTF-8/16

Unicode在很长一段时间内无法推广,直到互联网的出现,为解决Unicode如何在网络上传输的问题,于是面向传输的众多UTF(UCS Transfer Format)标准出现了,顾名思义, UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。 UTF-8就是在互联网上使用最广的一种Unicode的实现方式,这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。

UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度,当字符在ASCII 码的范围时,就用一个字节表示。对于中文字符,Unicode一个中文字符占2个字节,而UTF-8中一个中文字符占3个字节。从Unicode到UTF-8并不是直接的对应,而是要过如下的映射规则来转换

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

                               二、Python语言中的编码

在Python语言中,字符串类有两种:str和unicode,这两个类均继承自basestring类。其中str以字节的方式存储字符串,其内容由字符串对应的文字及编码方式决定。unicode是以16bit为一个单位保存字符串对应的文字。从上面编码发展史的内容中我们可以看出,Unicode与其他编码方式的对应关系是这样的:

Python2中的中文字符编解码浅析

在Python中,str对象保存的是基于ASNI扩展的编码方式的字符串,不同编码方式之间可以使用Unicode作为中介来互相转换。但实际操作过程中,情况比上图要复杂

源文件的编码

比如如下Python源文件,执行会报错

Python2中的中文字符编解码浅析

Python2中的中文字符编解码浅析

这是因为源文件中出现了中文,但没有指定源文件的编码方式,Python解释器会使用默认的ASCII对源文件解码,当然也就没办法处理中文。

解决的方法很简单,在源文件第一行或第二行加上编码注释,Linux下建议使用utf-8,中文版Windows下建议使用GBK

Python2中的中文字符编解码浅析

不同编码之间的转换

例如如下代码,本意是将utf8编码的字符串转换为gbk编码

Python2中的中文字符编解码浅析 但在执行中会报错

Python2中的中文字符编解码浅析

对照上面Python的编码解码示意图可知,这是因为在源码中没有指定默认的解码方式,

a_utf8.encode(‘gbk’)等价于a_utf8.decode(defaultencoding).encode(‘gbk’)

Python解释器会使用默认的解码方式(默认defaultencoding为ASCII)将a_utf8字符串解码到Unicode字符串,因为汉字的编码超过了ASCII的范围,会发生报错

解决的方法很简单,重新设置一下默认的编码方式即可

Python2中的中文字符编解码浅析 如果不愿意或不方便修改默认的编码方式,也可以使用明文的编码方式来进行编解码

Python2中的中文字符编解码浅析

Json中的字符串

Python自带了一个处理JSON数据的库——json,json库中最常用的是dumps和loads方法。

在默认参数的情况下,dumps的返回值为str类型,字符串被序列化后的表示方式为“/uxxxx”,其中xxxx为文字对应的unicode编码的十六进制表示方式。

dumps函数的encoding参数的默认值为“utf-8”,当待序列化的结构化数据中的字符串编码类型不是utf-8编码时,需显式指定encoding的值。

Python2中的中文字符编解码浅析 Python2中的中文字符编解码浅析 loads函数的入参为str类型的json格式字符串,当字符串的编码不是utf-8时,需要手工指定字符串的编码方式。loads返回的结构化数据中,字符串均为unicode实例

Python2中的中文字符编解码浅析

Python2中的中文字符编解码浅析

                                           三、处理编码的建议

Python中处理中文编码的一些建议

基本设置

主动设置defaultencoding。(默认的是ascii)

代码文件的保存格式要与文件头部的# coding:xxx一致。

如果是中文,程序内部尽量使用unicode,而不用str。

关于打印

你在打印str的时候,实际就是直接将字节流发送给shell。如果你的字节流编码格式与shell的编码格式不相同,就会乱码。

而你在打印unicode的时候,系统自动将其编码为shell的编码格式,是不会出现乱码的。

程序内外要统一

如果说程序内部要保证只用unicode,那么在从外部读如字节流的时候,一定要将这些字节流转化为unicode,在后面的代码中去处理unicode,而不是str。

                                四、结语

Python中处理中文编码的关键是清晰地明白自己的目的:读入什么格式的编码,声明的字节是什么格式的,str到unicode是怎样转换的,str的两种编码又是如何转换的。最重要的,是要自己主动去维护一种统一。

版权所属,禁止转载

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址