本节书摘来自异步社区《Python Cookbook(第2版)中文版》一书中的第1章,第1.20节,作者[美]Alex Martelli , Anna Martelli Ravenscrof , David Ascher ,高铁军 译,更多章节内容可以访问云栖社区“异步社区”公众号查看。
1.20 使用Unicode来处理国际化文本
任务
需要处理包含了非ASCII字符的文本字符串。
解决方案
可以在一些使用普通的字节串str类型的场合,使用Python提供的内置的unicode类型。用法很简单,只要接受了在字节串和unicode字符串之间的显式转换的方式:
>>> german_ae = unicode('\xc3\xa4', 'utf8')
这里german_ae是一个unicode字符串,代表了小写的德语元音变音(umlaut,或其他分音符)字符“æ”。根据指定的UTF-8编码方式,通过解析单字节字符串'xc3xa4',这段代码创建了一个unicode字符串。还有很多其他的编码方式,不过UTF-8最常用,因为它是最通用的(UTF-8可以编码任何unicode字符串),而且也和7位的ASCII字符集兼容(任何ASCII单字节字符串,也是正确的UTF-8编码字符串)。
一旦跨过这一屏障,生活就变得更美好了!可以像处理普通的str字符串那样操纵unicode字符串:
>>> sentence = "This is a " + german_ae
>>> sentence2 = "Easy!"
>>> para = ". ".join([sentence, sentence2])
注意,para是一个unicode字符串,这是因为一个unicode字符串和一个字节串之间的操作总会产生一个unicode字符串—除非这个操作发生错误并抛出异常:
>>> bytestring = '\xc3\xa4' #某个非ASCII字节串
>>> german_ae += bytestring
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in
position 0: ordinal not in range(128)
字符’0xc3’不是7位ASCII编码中的有效字符,Python也拒绝猜测其编码。所以,在Python中使用unicode的关键点是,你要随时明确编码是什么。
讨论
如果你遵守一些规范,并且学会处理一些常见的问题,则Python中的unicode处理是非常简单的事情。这不是说完成一个高效的Unicode实现是个简单的任务。不过,正如其他的一些难题一样,无须担心太多:只管使用Python的高效的Unicode实现就行了。
最重要的一点是,首先要完全接受字节串和unicode字符串的差异。正如解决方案小节所示,你经常需要通过一个字节串和一个编码方式显式地创建一个unicode字符串。不指定编码方式,字节串基本没有什么意义,除非你很有运气而且碰巧那个字节串是ASCII文本。
在Python中使用unicode字符串的最常见的问题是,你正在处理的文本一部分是unicode对象,另一部分则是字节串。Python会简单地尝试把你的字节串隐式地转换成unicode。它通常假设那些是ASCII编码,如果其中碰巧含有了非ASCII字符,它会给你一个UnicodeDecodeError的异常。UnicodeDecodeError异常通知你,你把Unicode和字节串混在了一起,而且Python无法(它根本也不会去尝试)猜测你的字节串代表何种文本。
各个Python大项目的开发人员们总结出了一些简单的规则,来避免这种运行时的UnicodeDecodeError异常,该规则可以被总结为一句话:总是在IO动作的关口做转换。下面更深入地解释一下。
- 无论何时,当你的程序接收到了来自“外部”的文本数据(来自网络、文件、或者用户输入等)时,应当立刻创建一个unicode对象,找出最适合的编码,如查看HTTP头,或者寻找一个合适的转化方法来确定所用的编码方式。
- 无论何时,当你的程序需要向“外部”发送文本数据(发到网络、写入文件、或者输出给用户等)时,应当探察正确的编码,并用那种编码将你的文本转化成字节串。(否则,Python会尝试把Unicode转成ASCII字节串,这很有可能发生UnicodeEncodeError异常,正好是前面例子中给出UnicodeDecodeError的相反情况)。
遵循这两个规则,可以解决绝大多数的Unicode问题。如果你仍然遇到了那两种UnicodeError之一,应当赶快检查是否忘记了在什么地方创建一个unicode对象,或者忘记了把它转化为编码过的字节串,再或者使用了完全不正确的编码方式。(编码错误也有可能来自于用户,或者其他与你的程序进行交互的程序,因为它们没有遵循编码规则或惯例。)
为了将一个Unicode字符串转回到编码过的字节串,你通常可以这么做:
>>> bytestring = german_ae.decode('latin1')
>>> bytestring
'\xe4'
现在,bytestring是德语中的用’latin1’进行编码的æ字符。注意,’xe4’(Latin1)以及前面展示的’xc3xa4’(UTF-8)代表了同样的德语字符,但使用了不同的编码。
至此为止,应该能够了解为什么Python拒绝在几百种可能的编码中进行猜测了吧。这是一种很重要的设计选择,基于了Zen of Python原则中的一条:“在模糊含混面前拒绝猜测。”在任何一个Python的交互式shell提示符下,输入import this语句,你就可以阅读Zen of Python中的重要原则。