每个用Python的人都肯定为编码头疼过,现在python2和3的str类型又有不同。所以专门写篇博客来记录python里关于Unicode的知识点和处理方法。

基础知识点

关于Unicode

  1. 我们通常意义上说的“字符”,是Unicode字符。
  2. 但计算机存储器里存的是字节序列,中间需要经过一层编码,并不是Unicode字符码位的二进制数据。
  3. Unicode字符包括一个前缀’U+’及字符码位,该码位与操作系统、编码方式无关,是Unicode可以跨平台的原因所在。
  4. Unicode码位是4-6位的16进制数字,但只有10%的码位会有对应字符。
  5. 把码位转换成字节序列的过程是编码(encode),把字节序列转换成码位的过程是解码(decode)。
  6. 我们通常说的编码是Unicode到字节码的过程,而不是从自然语言到Unicode的过程,某种意义上,我们可以认为Unicode字符是人类可读的。
  7. 字符的存储空间占用与Unicode无关,只和编码方式有关,但是在有的编码方式里,不同的码位字符占用空间可能不同(比如utf-8)。
  8. 原则上,我们无法在不知道编码方式的情况下,将字节码还原成字符。实际上我们可以根据一些特征猜测。

Python中的字符与Unicode

  1. Python出现的比Unicode标准还早几年,所以你得理解早期别扭的设计。
  2. Python2的str对象是字节序列。
  3. Python3的str对象是Unicode,约等于Python2里面的unicode类型。
  4. Python3里的字节序列类型是bytes,形如b’ab\xc4’,里面看似有ASCII字符,但实际存储的都是0-255的整数,只是能显示ASCII字符会显示出来。
  5. Python2默认的编码方式是ASCII,Python3是utf-8。

关于常见编码方式

  1. 最常见的编码方式有这几种:utf-8,utf-16,latin1,除此外还有针对中文的gb2312,微软的cp1252,IBM的cp437,现在几乎没用了,知道一下就行。
  2. 绝地大多数情况下,我们使用的都是utf-8。
  3. utf-16在开头会有形如b’\xff\xfeE\x00l\x00’的字节序列,是BOM,byte-order mark,字节序标记。大小字节序和CPU有关。使用确定字节序的编码方式UTF-16LE(小字节序),UTF-16BE(大字节序)可以避免出现BOM。
  4. utf-8的优势在于在不同平台的字节序都一致,根本不需要声明字节序,但Windows notepad还是会在utf-8的文件里增加BOM头b’\xef\xbb\xbf’,这很傻逼的处理方式会导致一些问题。
  5. gb2312是过时的中文编码方式,但很多文件(尤其Windows平台的)还会使用,如果中文用utf-8解析不出来,十有八九就是用的gb2312。

一些处理办法

处理时机

  1. 尽量保证字节码只出现在输入和最终输出的地方,业务逻辑里保证处理的都是unicode字符,可以极大的减少混乱度。
  2. 可以在入口处使用setdefaultencoding来直接改变默认编码方式,如下

    1
    2
    reload(sys)
    sys.setdefaultencoding('utf-8')
  3. 读取文件后尽早处理为unicode,或者直接开unicode通道。可以使用codecs库来直接开unicode通道,如下 file = codecs.open("test", "r", "utf-8")

特殊情况

其实编码导致的特殊情况有很多但我多数都忘了,记起来一个来补充一个

  1. s = '\u5f20\u4e09'形如这种情况,看似是个unicode,实际上s的类型是str,注意前面没有u,所以你继续用常用编码方式encode不会有任何改变,decode的话只会将\转为\\,结果还是无效,这时候需要的解码方法是s.decode('unicode_escape'),可以将unicode码位的字符串转成真正的unicode对象

unicodedata库

如果你经常要处理非英文的拉丁语系语言,或者做搜索相关项目,这真是个好用到哭的python内置库。我深有感触。

  1. 形如café这种带着重音符号的拉丁文词组,想要当成英文处理,可以用unicodedata库的unicodedata.normalize('NFKD', 'café'),将重音符号挑出来,然后再去掉特殊符号,从而变成ASCII范围内的英文。
  2. unicodedata.normalize第一个参数有4个可选项NFC/NFD/NFKC/NFKD,NFC会把重音符合合并到字母上,NFD相反,会拆分出来,NFKC和NFKD同上,但是会做兼容处理。比如符号’μ’会转成希腊字母’μ’,还有二分之一那个符号(\u00bd),会被转成1/2。这对于提升搜索体验很有意义。
  3. unicodedata.name(uchar)可以显示特殊字符的名称,也可以用unicodedata.lookup(name)根据名称找到对应的字符。比如

    1
    2
    >>> name(u'\u00bd')
    'VULGAR FRACTION ONE HALF'
  4. unicodedata.numeric(uchar)会将内容直接转成对应的数字,比如,这个函数也能将罗马数字、中文数字、中文繁体数字、特殊符号数字都转成标准float类型,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    >>> numeric(u'\u00bd')
    0.5
    >>> numeric(u'①')
    1.0
    >>> numeric(u'貮')
    2.0
    >>> numeric(u'三')
    3.0
  5. unicodedata.category(uchar)可以区分字符的类型,有以下几种:

    1
    2
    3
    4
    5
    6
    Lu(letter,uppercase):大写字母。 
    Ll(letter,lowercase):小写字母。
    Lt(letter,titlecase):词首字母大写的字母。
    Lm(letter,modifier):修饰符字母字符,它是独立式的间距字符,指示前面字母的修改。
    Lo(letter,other):不属于大写字母、小写字母、词首字母大写或修饰符字母的字母。
    Nd(number,decimal digit):十进制数字字符,即范围 0 到 9 内的字符。

暂时就这些了,大家有想到的,或者发现的误区欢迎在评论区提出来。