根据对Unicode的支持情况,编程语言可以划分为4类:
- 在Unicode出现或流行之前编写的语言。C和C++就属于这一类。这类语言对unicode的支持参差不齐。或没有内置到语言中,或很难正确的使用。因此开发者常常会用错。
- 对Unicode支持稍好一点。这些语言在Unicode广泛流行后才出现的,但语言中对unicode的操作方式是严重错误的。虽然这些语言诞生较晚,但依然含有第一类语言中的所有缺点。以我的经验,其中代表语言就是PHP。尽管还有其他语言也同样糟糕。
- 对Unicode支持基本正确,但有少数致命缺点的语言。这一类语言比较“现代”,且能理解Unicode,但依然无法让开发者正确的处理unicode,导致在这些语言中对unicode会出现一些严重不足。让我很沮丧的是,Python 2.x就属于这一类(下文会详细介绍)。
- 能正确处理Unicode的语言。这些语言完全支持Unicode,可以用Unicode方便快速的完成任务,且不易出错。Java和.NET平台就属于这一类语言。
那么,Unicode到底是什么,我们在Unicode上犯了哪些错误?Joel这篇The absolute minimum every software developer absolutely, positively must know about unicode绝对是每个软件开发者必须阅读的文章。为了为简洁起见,以及照顾那些天生耐心不够的朋友,我会在本文中对其进行总结。
字符和字节
基本事实是,若想正确的处理文本,就必须了解字符的抽象概念。不严谨的定义一下,字符表示的是文本中的单个符号。更重要的是,一个字符不是一个字节。我再强调一遍!一个字符不是一个字节!!!而且,一个字符有许多表示方法,不同的表示方法会使用不同的字节数。就像前面我说的那样,字符就是文本中最小的单元。
U+0041
。而欧元符号的codepoint是U+20A0
,其他类似。一个文本字符串就是这样一系列的codepoint,表示字符串中每个字符元素。
当然,你迟早会需要储存和传输这些理论上的Unicode字符串。如果选择一种其他人可以理解的方式以字节方式进行表示,就可以以大家都理解的方式互相发送文本。这里就需要引入字符编码(encoding)。
字符编码是在理想的字符和实际的字节表示方法之间的映射。这种映射无需面面俱到,即在某种编码中也许无法表示一些特定的字符。同时也无须为每个字符使用相同的内存空间,譬如某些字符使用单字节编码,而其他字符需要多个字节。
由于同一个字符的字节表现形式不止一种。这意味着当遇到了一串字节,如果不知道使用的是什么编码,即使知道这些字节表示的是文本,也不知道是什么意思。所能做的就是猜使用的编码。简而言之,字节不是文本。即使忘了文中介绍的所有内容,也要记住这句话。为了读写文本,归根结底就是要知道其中使用的编码方式,不管是从约定、标识信息、或是其他方法得知。
Python是如何处理Unicode
从这里开始介绍Python的Unicode支持。在Python的类型层次中,有3种不同的字符串类型:“unicode”,表示Unicode字符串(文本字符串)、“str”,表示字节字符串(二进制数据);“basestring”。表示前两种字符串类型的父类。在我看来,Python在这里犯了一个错误,根据前面的定义,这让Python成为第三类语言,而没有成为第四类。
我用了很长的篇幅苦口婆心的强调字节和字符在本质上是不同的东西,只有通过字符编码才能互相转换。但不幸的是,Python犯了两个互不相关的错误,轻轻松松的就会让你忘掉这些。
第一个错误的严重性值得商榷:即将一串字节视为字符串。是否应该这样做还有争议。Java和,NET认为这样做是不对的,而其他一些语言却持有相反的态度。无论如何,你可能希望对文本进行某些操作,如正则匹配、字符串替代等。将这些操作应用到字节序列上都是没有意义的。而Python将字节序列作为另一种类型的字符串对待,允许在这两者上执行同样的操作。
sys.setdefaultencoding()
指定。在大多数平台上,默认的是ASCII编码。但对于所有转换,使用这种编码几乎都是错误的。如果不手动指定编码就调用str()
或unicode()
,或是函数以字符串作为参数,但传递的是其他类型的参数时,都会使用这个默认编码。sys.setdefaultencoding()
将默认的编码设置为真正会用到的编码。但这样仅仅是将问题隐藏起来,虽然这样刚开始能解决一些文本处理问题。但缺乏实际可行性,因为许多应用,特别是网络应用,在不同的地方会使用不同的文本编码。
正确的解决方法是修改代码,以正确的方式处理文本。下面是一些应该做到的指导性意见:
- 所有文本字符串都应该是unicode类型,而不是str类型。如果处理的是文本,而变量类型是str,这就是bug了!
- 若要将字节串解码成字符串,需要使用正确的解码,即
var.decode(encoding)
- (如,
var.decode('utf-8')
- )。将文本字符串编码成字节,使用var.encode(encoding)。
- 永远不要对unicode字符串使用
str()
- ,也不要在不指定编码的情况下就对字节串使用
unicode()
- 。
- 当应用从外部读取数据时,应将其视为字节串,即str类型的,接着调用
.decode()
- 将其解释成文本。同样,在将文本发送到外部时,总是对文本调用
.encode()
- 。
- 如果代码中使用字符串字面值来表示文本,总是应该含有’u'前缀。但实际上,永远不要在代码中定义原始的字符串字面值。不管怎样,我自己是很讨厌这一条,也许其他人也和我一样吧。