专栏首页腾讯移动品质中心TMQ的专栏Python2中的中文字符编解码浅析

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方案来保存英文文字。

后来,世界各国都开始使用计算机,但很多国家使用的语言不是英文,里面的字母在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的两种编码又是如何转换的。最重要的,是要自己主动去维护一种统一。

本文分享自微信公众号 - 腾讯移动品质中心TMQ(gh_2052d3e8c27d),作者:吴昊

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2016-12-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 从Java乱码谈起

    在实际项目开发中,特别是涉及到中文输入输出的时候,大家肯定都被各种乱码问题坑过。如果遇到复杂的系统,为了乱码问题折腾几天也不是不可能。

    腾讯移动品质中心TMQ
  • 腾讯TMQ在线沙龙回顾|FAT——专业服务于微信H5/小程序UI自动化测试

    活动介绍 TMQ第四十六期在线沙龙分享活动圆满结束啦! ? 本次分享的主题:FAT——专业服务于微信H5/小程序UI自动化测试 共有295位测试小伙伴报名参加...

    腾讯移动品质中心TMQ
  • 你的自动化测试在win10上跑不起来了吗?

    【问题描述】 你有没有遇到这样的问题呢:自动化测试在win7、xp系统上运行好好的,到win10系统上却一直失败呢? 仔细观察运行失败的原因,发现自动化测试中有...

    腾讯移动品质中心TMQ
  • 各种字符编码详解

    相信很多人编程中经常遇到编码问题,也在这上面踩了不少坑。花了点时间把所有常用的编码搞清楚。

    serena
  • 字符串和编码

    字符编码 我们已经讲过了,字符串也是一种数据类型,但是,字符串比较特殊的是还有一个编码问题。 因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才...

    wangxl
  • Python常用字符编码

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    于小勇
  • python编码问题

    基本常识 ASCII编码是1个字节bytes,而Unicode编码通常是2个字节 1bytes=8bit 在计算机内存中,统一使用Unicode编码,当需要保存...

    py3study
  • 第二章(1.3)Python基础知识(输入输出)

    Python提供了一个raw_inpu,可以让用户输入字符串,并存放到一个变量里。比如输入用户的名字:

    两只橙
  • python基础-字符串与编码

    转载于:廖雪峰的官方网站-python教程 字符编码 我们已经讲过了,字符串也是一种数据类型,但是,字符串比较特殊的是还有一个编码问题。 因为计算机只能处理数字...

    昱良
  • Unicode?utf-8?GB2312?

    分享一点关于字符编码的来源的知识,是前段时间在廖雪峰老师的python教程里看到的,觉得很通俗易懂,现在复制了过来分享给各位没看过这个教程的朋友们。Unico...

    benny

扫码关注云+社区

领取腾讯云代金券