在Python中正确使⽤Unicode
正确处理⽂本,特别是正确处理Unicode。是个⽼⽣常谈的问题,有时甚⾄会难倒经验丰富的开发者。并不是因为这个问题很难,⽽是因为对软件中的⽂本,开发者没有正确理解⼀些关键概念及其表⽰⽅法。在StackOverflow上搜索关于UnicodeDecodeError相关的问题,可以看到很多⼈都有这样的误解。这些错误的概念可以追溯到Unicode出现之前。那时许多现今的开发者还没⼊职,也包括我⾃⼰。如果这些错误的概念没有散布开来,其实不是个问题。现在很多⼈都有这些错误概念,部分原因是因为有些⾮常流⾏的语⾔传播,甚⾄固化了这些错误概念,使得纠正起来反⽽变得很困难。
根据对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绝对是每个软件开发者必须阅读的⽂章。为了为简洁起见,以及照顾那些天⽣耐⼼不够的朋友,我会在本⽂中对其进⾏总结。
字符和字节
基本事实是,若想正确的处理⽂本,就必须了解字符的抽象概念。不严谨的定义⼀下,字符表⽰的是⽂本中的单个符号。更重要的是,⼀个字符不是⼀个字节。我再强调⼀遍!⼀个字符不是⼀个字节⽽且,⼀个字符有许多表⽰⽅法,不同的表⽰⽅法会使⽤不同的字节数。就像前⾯我说的那样,字符就是⽂本中最⼩的单元。
Unicode以⼤家都认可的⽅式定义了⼀系列的字符。可以将Unicode理解成⼀个字符数据库,每个字符都与唯⼀的数字关联,称为code point。这样,英⽂⼤写字母A的codepoint是U+0041。⽽欧元符号的code
point是U+20A0,其他类似。⼀个⽂本字符串就是这样⼀系列的codepoint,表⽰字符串中每个字符元素。
当然,你迟早会需要储存和传输这些理论上的Unicode字符串。如果选择⼀种其他⼈可以理解的⽅式以字节⽅式进⾏表⽰,就可以以⼤家都理解的⽅式互相发送⽂本。这⾥就需要引⼊字符编码(encoding)。
字符编码是在理想的字符和实际的字节表⽰⽅法之间的映射。这种映射⽆需⾯⾯俱到,即在某种编码中也许⽆法表⽰⼀些特定的字符。同时也⽆须为每个字符使⽤相同的内存空间,譬如某些字符使⽤单字节编码,⽽其他字符需要多个字节。
由于同⼀个字符的字节表现形式不⽌⼀种。这意味着当遇到了⼀串字节,如果不知道使⽤的是什么编码,即使知道这些字节表⽰的是⽂本,也不知道是什么意思。所能做的就是猜使⽤的编码。简⽽⾔之,字节不是⽂本。即使忘了⽂中介绍的所有内容,也要记住这句话。为了读写⽂本,归根结底就是要知道其中使⽤的编码⽅式,不管是从约定、标识信息、或是其他⽅法得知。
Python是如何处理Unicode
从这⾥开始介绍Python的Unicode⽀持。在Python的类型层次中,有3种不同的字符串类型:“unicode”,
表⽰Unicode字符串(⽂本字符串)、“str”,表⽰字节字符串(⼆进制数据);“basestring”。表⽰前两种字符串类型的⽗类。在我看来,Python在这⾥犯了⼀个错误,根据前⾯的定义,这让Python成为第三类语⾔,⽽没有成为第四类。
我⽤了很长的篇幅苦⼝婆⼼的强调字节和字符在本质上是不同的东西,只有通过字符编码才能互相转换。但不幸的是,Python犯了两个互不相关的错误,轻轻松松的就会让你忘掉这些。
第⼀个错误的严重性值得商榷:即将⼀串字节视为字符串。是否应该这样做还有争议。Java和,NET认为这样做是不对的,⽽其他⼀些语⾔却持有相反的态度。⽆论如何,你可能希望对⽂本进⾏某些操作,如正则匹配、字符串替代等。将这些操作应⽤到字节序列上都是没有意义的。⽽Python将字节序列作为另⼀种类型的字符串对待,允许在这两者上执⾏同样的操作。
第⼆个错误的严重性⼤⼀些,Python试图在字节串和字符串之间以不为⼈所察觉的⽅式进⾏转化。在不同的转换中,在条件允许的情况下,Python会试图在字节串和unicode字符串直接进⾏转换。例如将字节串和unicode字节串连接到⼀起时。根据前⾯的介绍,不使⽤encoding就在不同类型之间进⾏转换是没有意义的。所以Python依赖⼀个“默认编码”,该编码由sys.setdefaultencoding()指定。在⼤多数平台上,默认的是ASCII编码。但对于所有转换,使⽤这种编码⼏乎都是错误的。如果不⼿动指定编码就调⽤str()或unicode(),或是函数以字符串作为参数,但传递的是其他类型的参数时,都会使⽤这个默认编码。
unicode所有字符
⾛出这个unicode困境的⼀个解决办法是,调⽤sys.setdefaultencoding()将默认的编码设置为真正会⽤到的编码。但这样仅仅是将问题隐藏起来,虽然这样刚开始能解决⼀些⽂本处理问题。但缺乏实际可⾏性,因为许多应⽤,特别是⽹络应⽤,在不同的地⽅会使⽤不同的⽂本编码。
正确的解决⽅法是修改代码,以正确的⽅式处理⽂本。下⾯是⼀些应该做到的指导性意见:
所有⽂本字符串都应该是unicode类型,⽽不是str类型。如果处理的是⽂本,⽽变量类型是str,这就是bug了!
若要将字节串解码成字符串,需要使⽤正确的解码,即var.decode(encoding)(如,var.decode('utf-8'))。将⽂本字符串编码成字节,使⽤de(encoding)。
永远不要对unicode字符串使⽤str(),也不要在不指定编码的情况下就对字节串使⽤unicode()。
当应⽤从外部读取数据时,应将其视为字节串,即str类型的,接着调⽤.decode()将其解释成⽂本。同样,在将⽂本发送到外部时,总是对⽂本调⽤.encode()。
如果代码中使⽤字符串字⾯值来表⽰⽂本,总是应该含有’u'前缀。但实际上,永远不要在代码中定义原始的字符串字⾯值。不管怎样,我⾃⼰是很讨厌这⼀条,也许其他⼈也和我⼀样吧。
顺便说⼀句,Python 3修复了这些问题,可以正确的处理unicode和字符串,这样Python就完全位于第四类中了,更多信息参见官⽅的更新说明中关于Unicode的部分。
希望这些内容能帮到你,如果对unicode到底是什么,如何处理unicode有疑惑的话,现在应该都清楚了。下次遇到UnicodeEncodeError或UnicodeDecodeError错误时,就应该完全知道问题出在哪,也知道如何去修复这些问题!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论