unicode和utf8

  • Unicode 是「字符集」字符集:为每一个「字符」分配一个唯一的 ID(学名为码位 / 码点 / Code Point)
  • UTF-8 是「编码规则」编码规则:将「码位」转换为字节序列的规则(编码/解码 可以理解为 加密/解密 的过程)

Unicode 字符集为每一个字符分配一个码位,例如「知」的码位是 ​​30693​​​,记作 ​​U+77E5​​​或​​\u77e5​​​(​​30693​​​ 的十六进制为 ​​0x77E5​​)。

UTF8编码做的是将对应的码位编码到字节序列。

U+ 0000 ~ U+ 007F: 0XXXXXXX
U+ 0080 ~ U+ 07FF: 110XXXXX 10XXXXXX
U+ 0800 ~ U+ FFFF: 1110XXXX 10XXXXXX 10XXXXXX
U+10000 ~ U+1FFFF: 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX

根据上表中的编码规则,之前的「知」字的码位 U+77E5 属于第三行的范围:

7    7    E    5    
0111 0111 1110 0101 二进制的 77E5
--------------------------
0111 011111 100101 二进制的 77E5
1110XXXX 10XXXXXX 10XXXXXX 模版(上表第三行)
11100111 10011111 10100101 代入模版
E 7 9 F A 5

这就是将 U+77E5 按照 UTF-8 编码为字节序列 E79FA5 的过程。

py2的str和unicode

py2的unicode就是字符对应的unicode码的字符串,例如​​上午​​​的unicode码就是​​\u4e0a\u5348​

s=u"上午"
u'\u4e0a\u5348'

所以说unicode是很好理解的就是没有经过编码的unicode码的字符串
str格式的本质含义则是“某种编码格式”,来看一下

s1="上午"
>> '\xe4\xb8\x8a\xe5\x8d\x88'

s1.decode('utf8')
>> u'\u4e0a\u5348'

s2="知"
>> '\xe7\x9f\xa5' # 可以看到是对应上面的例子

s2.decode('utf8')
>> u'\u77e5'

那就好理解了,unicode是没有经过编码的,而str是经过编码后的字节数组,两者转化就是编码解码的问题了。
我们把s1和s2解码可以看到就是unicode

s1="上午"
Out[7]: '\xe4\xb8\x8a\xe5\x8d\x88'

s1.decode('utf8')
Out[8]: u'\u4e0a\u5348'

s2="知"
Out[9]: '\xe7\x9f\xa5' # 可以看到是对应上面的例子

s2.decode('utf8')
Out[10]: u'\u77e5'

unicode再转为str,unicode 编码 encode

s1="上午"
s.decode('utf8').encode('utf8')
Out[11]: '\xe4\xb8\x8a\xe5\x8d\x88'

s2="知"
s2.decode('utf8').encode('utf8')
Out[12]: '\xe7\x9f\xa5'

我们用str对unicode执行编码是最容易出错的​​UnicodeEncodeError: 'ascii' codec can't encode character u'\u77e5' in position 0: ordinal not in range(128)​

s2=u"知"
str(s2)
UnicodeEncodeError: 'ascii' codec can't encode character u'\u77e5' in position 0: ordinal not in range(128)

这是因为我们对unicode执行编码,环境的默认编码是ascii,通过下面代码将环境的默认编码转为utf8,发现执行就没有问题了

reload(sys)
sys.setdefaultencoding('utf-8')
str(u"知")
Out[15]: '\xe7\x9f\xa5'

还有一种操作是对str字节码执行encode,这里其实是执行的​​str.decode.encode​​​,只要我们设置了默认的编码​​utf8​​后是不会报错的

"知".encode('gbk') # "知".decode('utf8').encode('gbk')
Out[16]: '\xd6\xaa'

最后一点,我们输入一个str的编码格式与操作系统有关系,window通常是gbk,linux通常是utf8

相信通过上面例子,再也不会怕看到​​UnicodeEncodeError: 'ascii' codec can't encode character​

我们在没有设置py2默认编码时,比较一个str和unicode会出现如下提示:

>>>u'为' == '为'
False
UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal

​UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal​​​ 我们可以运行​​'为'.decode()​​,会提示错误:
​UnicodeDecodeError: 'ascii' codec can't decode byte 0xe9 in position 0: ordinal not in range(128)​​ 这其实就是上述warning的来源,当decode失败,默认认为str和unicode ​​not equal​​ 我们加上

>>>import sys
>>>reload(sys)
>>>sys.setdefaultencoding('utf8')

>>>u'为' == '为'
True