首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python2中的中文字符编解码浅析

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

作者头像
腾讯移动品质中心TMQ
发布2018-02-08 10:51:19
1.4K0
发布2018-02-08 10:51:19
举报

自动化测试过程中,输入文本、读取文件、解析网络请求、字符串断言、正则匹配这些步骤都是必不可少的。而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方案来保存英文文字。

后来,世界各国都开始使用计算机,但很多国家使用的语言不是英文,里面的字母在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与其他编码方式的对应关系是这样的:

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

源文件的编码

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

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

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

不同编码之间的转换

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

但在执行中会报错

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

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

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

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

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

Json中的字符串

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

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

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

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

三、处理编码的建议

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

基本设置

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

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

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

关于打印

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

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

程序内外要统一

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

四、结语

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2016-12-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯移动品质中心TMQ 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、编码发展史
  • 二、Python语言中的编码
  • 三、处理编码的建议
    • Python中处理中文编码的一些建议
    • 四、结语
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档