Python-正确使用Unicode

正确处理文本,特别是正确处理Unicode。是个老生常谈的问题,有时甚至会难倒经验丰富的开发者。并非由于这个问题很难,而是由于对软件中的文本,开发者没有正确理解一些关键概念及其表示方法。在StackOverflow上搜索关于UnicodeDecodeError相 关的问题,能够看到不少人都有这样的误解。这些错误的概念能够追溯到Unicode出现以前。那时许多现今的开发者还没入职,也包括我本身。若是这些错误 的概念没有散布开来,其实不是个问题。如今不少人都有这些错误概念,部分缘由是由于有些很是流行的语言传播,甚至固化了这些错误概念,使得纠正起来反而变 得很困难。html

根据对Unicode的支持状况,编程语言能够划分为4类:python

  • 在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绝对是每一个软件开发者必须阅读的文章。为了为简洁起见,以及照顾那些天生耐心不够的朋友,我会在本文中对其进行总结。数据库

字符和字节

基本事实是,若想正确的处理文本,就必须了解字符的抽象概念。不严谨的定义一下,字符表示的是文本中的单个符号。更重要的是,一个字符不是一个字 节。我再强调一遍!一个字符不是一个字节!!!并且,一个字符有许多表示方法,不一样的表示方法会使用不一样的字节数。就像前面我说的那样,字符就是文本中最 小的单元。编程

Unicode以你们都承认的方式定义了一系列的字符。能够将Unicode理解成一个字符数据库,每一个字符都与惟一的数字关联,称为code point。这样,英文大写字母A的codepoint是U+0041。而欧元符号的codepoint是U+20A0,其余相似。一个文本字符串就是这样一系列的codepoint,表示字符串中每一个字符元素。网络

固然,你早晚会须要储存和传输这些理论上的Unicode字符串。若是选择一种其余人能够理解的方式以字节方式进行表示,就能够以你们都理解的方式互相发送文本。这里就须要引入字符编码(encoding)。编程语言

字符编码是在理想的字符和实际的字节表示方法之间的映射。这种映射无需面面俱到,即在某种编码中也许没法表示一些特定的字符。同时也无须为每一个字符使用相同的内存空间,譬如某些字符使用单字节编码,而其余字符须要多个字节。函数

因为同一个字符的字节表现形式不止一种。这意味着当遇到了一串字节,若是不知道使用的是什么编码,即便知道这些字节表示的是文本,也不知道是什么意 思。所能作的就是猜使用的编码。简而言之,字节不是文本。即便忘了文中介绍的全部内容,也要记住这句话。为了读写文本,归根结底就是要知道其中使用的编码 方式,不论是从约定、标识信息、或是其余方法得知。编码

Python是如何处理Unicode

从这里开始介绍Python的Unicode支持。在Python的类型层次中,有3种不一样的字符串类型:“unicode”,表示Unicode 字符串(文本字符串)、“str”,表示字节字符串(二进制数据);“basestring”。表示前两种字符串类型的父类。在我看来,Python在这 里犯了一个错误,根据前面的定义,这让Python成为第三类语言,而没有成为第四类。code

我用了很长的篇幅苦口婆心的强调字节和字符在本质上是不一样的东西,只有经过字符编码才能互相转换。但不幸的是,Python犯了两个互不相关的错误,轻轻松松的就会让你忘掉这些。htm

第一个错误的严重性值得商榷:即将一串字节视为字符串。是否应该这样作还有争议。Java和,NET认为这样作是不对的,而其余一些语言却持有相反 的态度。不管如何,你可能但愿对文本进行某些操做,如正则匹配、字符串替代等。将这些操做应用到字节序列上都是没有意义的。而Python将字节序列做为 另外一种类型的字符串对待,容许在这二者上执行一样的操做。

第二个错误的严重性大一些,Python试图在字节串和字符串之间以不为人所察觉的方式进行转化。在不一样的转换中,在条件容许的状况 下,Python会试图在字节串和unicode字符串直接进行转换。例如将字节串和unicode字节串链接到一块儿时。根据前面的介绍,不使用 encoding就在不一样类型之间进行转换是没有意义的。因此Python依赖一个“默认编码”,该编码由sys.setdefaultencoding()指定。在大多数平台上,默认的是ASCII编码。但对于全部转换,使用这种编码几乎都是错误的。若是不手动指定编码就调用str()unicode(),或是函数以字符串做为参数,但传递的是其余类型的参数时,都会使用这个默认编码。

走出这个unicode困境的一个解决办法是,调用sys.setdefaultencoding()将默认的编码设置为真正会用到的编码。但这样仅仅是将问题隐藏起来,虽然这样刚开始能解决一些文本处理问题。但缺少实际可行性,由于许多应用,特别是网络应用,在不一样的地方会使用不一样的文本编码。

正确的解决方法是修改代码,以正确的方式处理文本。下面是一些应该作到的指导性意见:

  • 全部文本字符串都应该是unicode类型,而不是str类型。若是处理的是文本,而变量类型是str,这就是bug了!
  • 若要将字节串解码成字符串,须要使用正确的解码,即var.decode(encoding)(如,var.decode('utf-8'))。将文本字符串编码成字节,使用var.encode(encoding)。
  • 永远不要对unicode字符串使用str(),也不要在不指定编码的状况下就对字节串使用unicode()
  • 当应用从外部读取数据时,应将其视为字节串,即str类型的,接着调用.decode()将其解释成文本。一样,在将文本发送到外部时,老是对文本调用.encode()
  • 若是代码中使用字符串字面值来表示文本,老是应该含有’u'前缀。但实际上,永远不要在代码中定义原始的字符串字面值。无论怎样,我本身是很讨厌这一条,也许其余人也和我同样吧。

顺便说一句,Python 3修复了这些问题,能够正确的处理unicode和字符串,这样Python就彻底位于第四类中了,更多信息参见官方的更新说明中关于Unicode的部分。

但愿这些内容能帮到你,若是对unicode究竟是什么,如何处理unicode有疑惑的话,如今应该都清楚了。下次遇到UnicodeEncodeErrorUnicodeDecodeError错误时,就应该彻底知道问题出在哪,也知道如何去修复这些问题!

相关文章
相关标签/搜索