Tuesday, January 6, 2009

python下的unicode object是个好东西

一说到mbcs(multiple bytes character set),就让人心烦.
从开始学习程序设计以来就不停地要面对这个问题.
这貌似是个鸡生蛋蛋生鸡的问题.
学习程序设计要考虑到编码问题;而编码问题如何处理又要先学会程序设计.
字符集(character set)和编码(encoding)概念的混淆实在害了不少人.
区域(locale)和各种乱七八糟的标准(standard)也让大家一路上混乱得可以(ANSI,ISO,IETF,....).

首先是字符集和编码.我觉得很有必要严格分清楚这两样东西,即使有时它们的确是``相等''的,
正如1+1与4/2一般,虽然表示的是同一个意义,但是严格来说它们不是同一样东西.
字符集顾名思义表示的是一个字符的集合,定义了某种语言包含的字符,是一个抽象的东西,
表达的是一种无形的信息. 然而在计算机内要具体表示它们,则需要有一个具体的表示方法,
在当代的存储程序型计算机内,就是如何用0,1来表示一个集合内的东西.因此谈到字符集就要涉及到编码.
在计算机发展的最初阶段,根本就没有字符集与编码的概念。那时所有的东西,在计算机里都是用ASCII存储的。
后来有了ANSI标准(本地化),在不同区域(locale)的系统里,使用字符集中的字符到具体编码的映射关系,
来表示对应字符集的编码。
最常见的自然是iso8859-1字符集了. 由于这个字符集只有一种编码方式,也即是ASCII,因此它们经常被混用.
而对于中文,GB2312,GBK,GB18030表示的是不同的字符集,然而对应的编码原则是一致的,都是用二字节的ascii value来编码一个中文字.
它们与ascii编码是"兼容"的. 对于日语,韩语等,也有不同的字符集与编码,而这些字符集与编码往往名称相同,因此往往被等同地混用(interchangeable)
不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。
"编码"的概念就是把"字符"转化成"字节",字符是抽象层面上的信息,而字节是具体存储表示的方法

Unicode的引进解决了不同字符集之间的不兼容,即使用一个超大的字符集来把世界上的语言都包含进去,给予一个统一的"编号".
由于其表示的范围极大(1,114,112个编码位,0 to 10FFFF),因此现在还有许多空位,以备以后需要而添加(甚至火星语也能编进去.)
比如u963f这个4位16进制数表示了'阿'这种中文字符.(开头的u表示一个unicode字符的开始)
ascii是使用最广泛的编码,因此很多编码方式都尽量与之相容.
因此可以看到u0030-u0039与ascii的0-9的值是一致的.
Unicode中文一般译作'统一码'或者'万国码'(都很恶心).是由ISO和unicode consortium这两个组织共同制定的标准,亦称为ISO/IEC 10646.
由于unicode太大了,而且很多是空的,常用的只占了那么一部分,存储起来很浪费空间.
因此有不同的''mapping'',对unicode进行编码(并且一般只映射了unicode的一个子集),
常见的有utf8,utf16等.它们就是unicode的编码.
这俩个都是使用变长的编码来表示字符,而utf8是现在unicode的事实标准.
一个例子:
u963f表示的是'阿',
阿这个字,在utf8里是e9 98 bf,而在utf16里是3f 96.
(事实上,在python里面,utf16得到的是'ff fe 3f 96',前面的两个byte是用来定义一个utf16的串的字节顺序的标记,所谓的BOM问题,即byte order mark)
而在gbk和gb2312,gb18030里都是b0 a2.
这些数据与u963f这个东西有本质的区别.
u963f是'阿'在unicode里面的"位置",而以上数据则是在计算机里面的实际表示方法.
(有一个很好的例子,见下)

----------分割线说:其实以上是introduction......--------------

写了这么多,似乎反客为主了....
python里面的u'hello'就可以得到一个unicode object,
所有的字符都可以利用类似方法存储为unicode表示.
然而具体编码是不同的.
比如我在一个utf8的终端下,输入
a=u'阿'
就得到了一个'阿'的unicode对象,放在a里面.
使用
a.encode('utf8')
a.encode('gbk')
等,则分别得到它的utf8和gbk编码数据.\
(这里接上面,即使我们看到,a这个东西表示`u963f',但是这不代表a本身在机器里面是存储为`u963f'.
而应该是某种具体的编码方法,如utf8等.

使用unicode方法可以从某个具体编码的数值得到一个unicode object.
比如
unicode( a.encode('utf8'), 'utf8')
则表示把a先编码为utf8,得到'阿'的utf8编码数值,然后再根据utf8反查得到
'阿'的unicode编码位,即u963f.
同理,使用decode方法,可以某个具体的编码数值转换为unicode对象.
如:
'阿'.decode('utf8')
可以得到u963f.(如果在gbk的终端,则必须使用gbk)

这里有一篇更详细的文章介绍此特性.
http://lenciel.cn/docs/unicode-in-python/

摘录几段我觉得要背下来的....
1. 处理任何编解码问题时我们都要牢记,unicode是为世界上所有的字符分配了一个码位(code point)的概念,而不是实现(字符在内存或者文件中的存在方式)。unicode占16位是绝对错误的(世界上语言如此多,码位早就超过百万个了)。

2. 要对unicode对象进行保存或者打印前,你要对它进行编码(encode)才行。

5. 正确的做法是,尽量早正确的decode一个str为unicode对象(如读入一个文件的内容,返回一个网页的内容等),并在你的程序里面全部使用unicode相关操作,直到你需要打印或者是写入文件时,再去encode它。

6. python提供了codec来减少我们的代码行数,它不是你乱码的救星:

f = open('small.html', "r")
bytes_in=f.read()
unicode_in=bytes_in.encode(utf-8)

===> fileObj = codecs.open( "small.html", "r", "utf-8" )


关于unicode,顺便还推荐这篇.
http://lenciel.cn/docs/unicode-complete/

-- 外一篇:GBK/GB2312 Python JOKE一则 --

问:为什么国家推出了GB2312之后要推出GBK?
答:Python的输出(under utf8)给了我们答案。

>>> u'�'.encode('gbk').decode('gb2312')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'gb2312' codec can't decode bytes in position 0-1: illegal multibyte sequence


No comments: