加入收藏 | 设为首页 | 会员中心 | 我要投稿 安卓应用网 (https://www.0791zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Python > 正文

Unicode和Python的中文处理

发布时间:2020-05-25 10:34:51 所属栏目:Python 来源:互联网
导读:在Python语言中,Uincode字符串处理一直是一个容易让人迷惑的问题。许多Python爱好者经常因为搞不清Unicode、UTF-8还有其它许许多多的编码之间的区别而大伤脑筋。笔者曾经也是这“伤脑筋一族”的成员,但经过半年多的

在Python语言中,Uincode字符串处理一直是一个容易让人迷惑的问题。许多Python爱好者经常因为搞不清Unicode、UTF-8还有其它许许多多的编码之间的区别而大伤脑筋。笔者曾经也是这“伤脑筋一族”的成员,但经过半年多的努力,现在终于初步弄清楚其中的一些关系。现将其整理如下,与各位同仁同享。同时也希望能借这篇短文抛砖引玉,吸引更多真正的高手加入进来,共同完善我们的Python中文环境。

本文所提到的各种观点,一部分是查阅资料所得,还有一部分是笔者利用已有各种编码数据用“猜测加验证”法得到。笔者自问才疏学浅,其中怕是藏有不少错误。各位看官中不乏高手,如果有哪一位发现其中哪里有错,万望各位高人不吝赐教。笔者自己丢丑事小,观点错误误了别人事大,因此各位大可不必顾忌笔者的面子问题。

第一节 文字编码和Unicode标准

要解释Unicode字符串就必须先从什么是Unicode编码开始说起。众所周知,文本显示一直是计算机显示功能必须解决的基本问题。而计算机并不识字,它实际上是把文本看做是一串“图片”,每张“图片”对应一个字符。每个计算机程序在显示文本时,必须借助一个记录这个文字“图片”如何显示的“图片”集合,从中找到每一个字符对应“图片”的数据,并依样画葫芦地把这个字“画”到屏幕上。这个“图片”就被称为“字模”,而记录字模显示数据的集合就被称为“字符集”。为方便程序查找,每个字符的字模数据在字符集中必须是有序排列的,而且每个字符都会被分配一个独一无二的ID,这个ID就是字符的编码。而在计算机进行字符数据处理时,总是用这个编码代表它表示的那个字符。因此,一个字符集就规定了一组计算机能够处理的字符数据。显然,不同国家指定的字符集大小不同,相应的字符编码也不同。

在计算机历史上,最为广泛使用的标准化字符集当首推ASCII字符集。它实际上是美国制订的标准,针对北美用户开发。它使用7个二进制位编码,可以表示128个字符。这个字符集最终被ISO组织正式采纳为国际标准,并且大量应用在各种计算机体系上。现如今,所有PC机的BIOS中都内含了ASCII字符集的字模,其深入人心可见一斑。

但是,当计算机在各个国家大规模普及时,ASCII编码的局限性就暴露出来了:它的字符空间实在有限,无法容纳更多的字符,可是绝大多数语言需要使用的字符数目都远不止128个。为了能正确处理本国文字,各个国家官方或民间纷纷开始了设计本国文字编码集的工作,并且最终涌现出许许多多针对各个国家文字的字符编码,如针对西欧字符的ISO-8859-1编码,针对简体中文的GB系列编码,还有针对日文的SHIFT-JIS编码等等。同时,为了保证各个新的字符集能够兼容原本的ASCII文本,大多数字符集不约而同地都将ASCII字符作为自己前128个字符,并使其编码与ASCII编码一一对应。

这样一来,各国文字的显示问题是解决了,可是又带来一个新的问题:乱码。不同国家、地区使用的字符集通常没有统一的规范进行约束,因此各个字符集编码往往互不兼容。同一个字在两个不同的字符集中编码一般不同;而同一个编码在不同的字符集中对应的字符也不一样。一段用编码A编写的文本在一个只支持编码B的系统上往往会被显示成一堆乱七八糟的字符。更糟糕的是,不同字符集使用的编码长度往往也不相同,那些只能处理单字节编码的程序在遇到双字节甚至是多字节编码的文本时,往往因为不能正确处理而产生了臭名昭著的“半个字”问题。这使得本已经混乱不堪的局面更是乱成了一团粥。

为了一劳永逸地解决这些问题,业界许多大公司和组织联合提出了一个标准,这就是Unicode。Unicode实际上是一种新的字符编码体系。它对字符集中的每个字符用两个字节长的ID号进行编码,从而规定出一个可容纳多达65536个字符的编码空间,并且将现今国际上各国编码中的常用字尽数收入罄中。由于在设计编码时有了周全的考虑,Unicode很好地解决了其它字符集在进行数据交流时的乱码和“半个字”问题。同时,Unicode的设计者充分考虑到现今大量字模数据使用的仍是各国制订的各种编码这一现实,提出了“将Unicode作为内部编码”的设计理念。也就是说,字符显示程序依然使用原先的编码和代码,而应用程序的内部逻辑使用的将是Unicode。当要进行文字显示时,程序总是将Unicode编码的字符串转换成原本的编码进行显示。这样,大家就不必为了使用Unicode而重新设计字模数据体系了。同时,为了与各国已经制订的编码相区别,Unicode的设计者将Unicode称为“宽字符编码”(wide characters encodings),而各国制订的编码习惯上被称为“多字节编码”(multi bypes encodings)。时至今日,Unicode体系又引入了四字节的扩展编码,并且逐渐与与UCS-4,也就是ISO10646编码规范合流,希望有朝一日能够用ISO10646体系统一全世界所有的文字编码。

Unicode体系甫一出世便被大家寄予厚望,并被迅速接受为ISO认可的国际标准。但是,Unicode在推广过程中却遭到了首先是欧美用户的反对。他们反对的理由非常简单:欧美用户原本使用的编码都是单字节长的,双字节的Unicode处理引擎无法处理原本的单字节数据;而如果要把现有的单字节文本全部转换成Unicode,工作量就太大了。再说,如果所有的单字节编码文本都被转换成双字节的Unicode编码,他们所有的文本数据占用的空间都会变成原来的两倍,而且所有的处理程序都要被重新编写。这个开销他们无法接受。

虽然Unicode是国际认可的标准,但是标准化组织不可能不考虑欧美用户这个最大的计算机使用群体的要求。于是在各方磋商之下,一个Unicode的变种版本产生了,这就是UTF-8。UTF-8是一个多字节的编码体系,它的编码规则如下:

1、UTF-8编码分为四个区:

一区为单字节编码,

编码格式为:0xxxxxxx;
对应Unicode:0x0000 - 0x007f

二区为双字节编码,

编码格式为:110xxxxx 10xxxxxx;

对应Unicode:0x0080 - 0x07ff

三区为三字节编码,

编码格式为:1110xxxx 10xxxxxxx 10xxxxxx

对应Unicode:0x0800 - 0xffff

四区为四字节编码,

编码格式为:11110xxx 10xxxxxxx 10xxxxxx 10xxxxxx

对应Unicode:0x00010000 - 0x0001ffff

五区为五字节编码,

编码格式为:111110xx 10xxxxxxx 10xxxxxxx 10xxxxxxx 10xxxxxxx

对应Unicode:0x00200000 - 0x03ffffff

六区为六字节编码,

编码格式为:111110x 10xxxxxxx 10xxxxxxx 10xxxxxxx 10xxxxxxx 10xxxxxxx

对应Unicode:0x04000000 - 0x7fffffff

其中,一、二、三区对应Unicode的双字节编码区,而四区则针对Unicode的四字节扩展部分(按照该定义,UTF-8还有五区和六区,但笔者并未在GNU glibc库中发现,不知为何);

2、各个区按照一、二、三、四、五、六顺序排列,其对应位置上的字符与Unicode保持相同;

3、不可显示的Unicode字符编码为0字节,换言之,它们没有被收入UTF-8(这是笔者从GNU C库注释中得到的说法,可能与实际情况不符);

按照UTF-8编码规则我们不难发现,其一区的128个编码实际上就是ASCII编码。所以UTF-8的处理引擎可以直接处理ASCII文本。但是,UTF-8对ASCII编码的兼容是以牺牲其它编码为代价的。比如,原本中、日、韩三国文字基本上都是双字节编码,但它们在Unicode编码中的位置对应到UTF-8中的三区,每一个字符编码要三个字节长。换句话说,如果我们把所有现有的中、日、韩三国编码的非ASCII字符文本数据转换成UTF-8编码,则其大小都会变成原来的1.5倍。

虽然笔者个人认为UTF-8的编码方式显得有些不够公平,但它毕竟解决了ASCII文本到Unicode世界的过渡问题,所以还是赢得了广泛的认可。典型的例子是XML和Java:XML文本的默认编码就是UTF-8,而Java源代码实际上就可以用UTF-8字符编写(JBuilder的用户应该有印象)。另外还有开源软件世界中大名鼎鼎的GTK 2.0,它使用UTF-8字符作为内部编码。

说了这么多,似乎话题有些扯远了,许多Python爱好者可能已经开始着急:“这和Python有什么关系呢?”好的,现在我们就把视线转到Python的世界来。

第二节 Python的Unicode编码系统

为了正确处理多语言文本,Python在2.0版后引入了Unicode字符串。从那时起,Python语言中的字符串就分为两种:一种是2.0版之前就已经使用很久的传统Python字符串,一种则是新的Unicode字符串。在Python语言中,我们使用unicode()内建函数对一个传统Python字符串进行“解码”,得到一个Unicode字符串,然后又通过Unicode字符串的encode()方法对这个Unicode字符串进行“编码”,将其“编码”成为传统Python字符串以上内容想必每一个Python用户都是烂熟于胸了。但是你可知道,Python的Unicode字符串并不是真正意义上的“Unicode编码的字符串”,而是遵循一种自己特有的规则。这个规则的内容简单得很:

1、ASCII字符的Python Unicode编码与它们的ASCII编码相同。也就是说,Python的Unicode字符串中ASCII文本仍然是单字节长度编码;

2、ASCII字符以外的字符,其编码就是Unicode标准编码的双字节(或四字节)编码。(笔者猜想,之所以Python社群要制订如此古怪的标准,可能是想保证ASCII字符串的通用性吧)

通常在Python应用中,Unicode字符串都是作为内部处理时使用,而终端显示工作则由传统的Python字符串完成(实际上,Python的print语句根本无法打印出双字节的Unicode编码字符)。在Python语言中,传统Python字符串就是所谓的“多字节编码”字符串,用于表示各种被“编码”成为具体字符集编码的字符串(比如GB、BIG5、KOI8-R、JIS、ISO-8859-1,当然也有UTF-8);而Python Unicode字符串则是“宽字符编码”字符串,表示从具体字符集编码中“解码”出来的Unicode数据。所以通常情况下,一个需要用到Unicode编码的Python应用往往会以如下方式处理字符串数据:

def foo(string,encoding = "gb2312"):
# 1. convert multi-byte string to wide character string
u_string = unicode(string,encoding)

# 2. do something
...

# 3. convert wide character string to printable multi-byte string
return u_string.encode(encoding)

(编辑:安卓应用网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读