Python Unicode和string那些事


开发过程中总是会碰到string,Unicode,ASCII,中文字符等编码的问题,本文力求搞清楚Python的string和Unicode到底是怎么回事!

基础知识

我们都知道计算机只认0和1,要想在计算机显示26个字母,就要给他们一套映射规则:计算机能认得的符号——>人类可读的符号,这转换的过程就是一套编码规则。

  • 字符集:就是一套字符的集合(比如中文4000个汉字集合)
  • 字符编码:一套法则,能够将0/1和人类的语言之间进行转换的法则

最初字符集比较少,ASCII码就够用了(一些控制符和26个字母),随着计算机的发展,各国语言都有自己独特的编码,汉字的编码也不断地扩展,从GBK到GB18030/DBCS,这个时候Unicode应运而生。

Unicode就是为了统一各国各地区的编码规则,重新搞了一套包罗地球上所有文化,符号的字符集!Unicode没有编码规则,只是一套包含全世界符号的字符集;Unicode也不完美,于是后续有了众多UTF编码(UTF-8,UTF-16)。

  • 总之搞清楚一件事情,一个字符用了UTF-8编码的,就要用UTF-8去解码,不然就会出现乱码。
文本处理

在Python-2.x,处理文本时,有string和Unicode两种类型

  • str类型就是一串bytes,这种类型跟C语言中处理string是非常相似的
  • Unicode是一串Unicode的数字映射(code point),用于映射某个字符与一个unicode的对应关系

看看代码出来是如何的:

>>> a = "简书"
>>> type(a)
<type 'str'>
>>> a
'\xe7\xae\x80\xe4\xb9\xa6'
>>> print a
简书
>>> 
>>> 
>>> u = u"简书"
>>> type(u)
<type 'unicode'>
>>> u
u'\u7b80\u4e66'
>>> print u
简书

从上面的代码可以看到,a = "简书"是string类型,可以看到a是一串'\xe7\xae\x80\xe4\xb9\xa6' byte字符,而u = u"简书" 是一串u'\u7b80\u4e66'的Unicode数字,通过print aprint u可以显示出中文字符。

常见问题#1

大家经常犯的一个错误就是混淆了Unicode以及通过Unicode编码存储在string里面的类型。比如上面的例子中u'\u7b80\u4e66'是Unicode,u'\u7b80\u4e66'是byte string,byte和Unicode之间一一对应,可以相互转换,转换规则如下:

>>> '\xe7\xae\x80'.decode('utf-8')
u'\u7b80'
>>> print '\xe7\xae\x80'.decode('utf-8')
简
>>> 
>>> u'\u7b80'.encode('utf-8')
'\xe7\xae\x80'
>>> print u'\u7b80'.encode('utf-8')
简

总结一下,上面例子中:

  • Unicode和byte都指
  • byte string 里面存储的是Unicode通过utf-8编码后得到的bytes
  • 所以byte string解码(decode)后即可得到Unicode
  • Unicode是byte string通过utf-8解码后得到的
  • Unicode用utf-8编码(encode)可以得到对应的bytes

Note:

总而言之 Unicode ———编码———>byte string

Unicode<———解码———byte string

Unicode就像是加密传输中的明文,可以用utf-8,utf-16,utf-7,utf-32等对Unicode进行加密,最后解密还是要用回原本的加密方式来解密,不然就解出乱码啦。

常见问题#2

对Unicode或者byte string编码解码方向搞错

>>> u'\u7b80'.decode('utf-8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "D:\appinstallation\AnacondaInstallation\envs\pyth
on2\lib\encodings\utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeEncodeError: 'ascii' codec can't encode character
u'\u7b80' in position 0: ordinal not in range(128)
>>>
>>>
>>> '\xe7\xae\x80'.encode('utf-8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe7
in position 0: ordinal not in range(128)

Unicode应该是进行编码的,如果进行decode,是会出现UnicodeEncodeError异常的,bytes string同理,应该进行解码,如果硬要进行编码的话,则抛出UnicodeDecodeError

常见问题#3

API调用不一致的问题,在调用别人的API的时候,需要看清楚是传Unicode还是byte string作为参数,因为第三方的API有的是支持Unicode,有的是byte string,甚至有的两种类型都支持,这个时候要清除自己传进去的参数是什么,比如一些变量值是从htpp requests里面拉过来的,这个时候你获得的变量值很有可能是Unicode类型(python requests get/post把返回值都转成了Unicode),而如果第三方的API需要byte string,那么就需要自己判断一下并进行转换,否则就会出现各种奇怪的UnicodeError

  • 虽然python社区规定了在所有的API中使用Unicode,但是少数一部分的API处于安全考虑还是要求使用byte string,需要注意一下。

常见问题#4

输出的类型不一致。
既然python社区推动到处使用Unicode,那么我们只要在开发过程中全部都转成Unicode是不是就万事大吉了?并不是,当你要输出文本到terminal或者到文件,这个文本必须是byte string类型的。
如果不是的话,python会隐式地帮你将Unicode转成string,python默认采用ASCII编码,而中文编码不在ASCII编码能够表示的范围之内,所以string无法将“你好”作为ASCII编码保存为str类型。
所以当你需要输出的时候,需要将你的Unicode转换成byte string再写文件,如果有中文的话,要用’utf-8’或’GBK’等支持中文的编码。