over 2 years ago

用Python 2.x会经常碰到一个错误:

UnicodeEncodeError: 'ascii' codec can't encode character ... : ordinal not in range(128)

搞清这个问题之前要先理解三个知识点:

  1. UTF-8 vs Unicode
  2. Encoding vs Decoding
  3. Python 2.7里的 str 和 unicode

1. UTF-8 vs Unicode

这一点已经在[之前的博文]里解释过了(http://cheng.logdown.com/posts/2015/01/14/utf-8-vs-unicode:),这里我来总结一下

  • Unicode 只是一个文字与数字之间的映射。比如,'汉' 这个字在Unicode里的代码是 ‘6C49’,想对应的数字就是 27721。地球上每种语言里的每一个文字都有这样一个相对应的数字标识。这个文字与数字的映射表就是 Unicode。
  • 当我们把这个映射后的数字存储在计算机上时,需要把它转换成 0 和 1. 我们可以简单的把 27721 转换成二进制代码 ’01101100 01001001‘ 来存储。但问题是计算机怎么能够知道这个两个字节是代表一个文字而不是两个文字? 这个时候就需要再有一种编码形式来告诉计算机将这个字节视为一个文字。这个编码就是UTF-8 (当然,UTF-8只是众多编码中的一种)

可以用这个顺序来理解:

屏幕上看到的文字     Unicode代码     根据UTF-8规范存在计算机内存或者硬盘里的模样
       汉      ->     6C49     ->       11100110 10110001 10001001

2. Encoding vs Decoding

在Python中把一个Unicode类转化为 0 和 1 的过程叫做Encoding。 把 0 和 1 反转为Unicode类的过程叫做Decoding。

在Python 2.7版本里,ASCII是默认的Encoding和Decoding的方法。

3. Python 2.7里的 str 和 unicode

当你把一个字符(不管该字符是英文字母还是ASCII里不能包含的字符)赋值给一个变量时:

han = '汉'

这个变量的类型都会是str:

In [113]:han = '汉'
         type(han)
Out[113]:str

但这里有很重要的一点需要理解

In [124]:han = '汉'
         bin_han = '\xe6\xb1\x89'
         han == bin_han # 虽然在界面里我们看到的是'汉'这个字,但其实它是一堆字符,并不是我们看到的文字

Out[124]:True

str这个类的本质其实就是原始字符数据(raw byte data)。它并不是我们所看到的'汉'!

那么Unicode类也是这样吗?

In [114]:uni_han= u'汉'
         type(uni_han)
Out[114]:unicode

In [131]:uni_han= u'汉'
         u_han = u'\u6c49'
         uni_han == u_han # 在Unicode中存储的是u'\u6c49'而不是你所看到的u'汉'

Out[131]:True

理解了以上三个知识点,我们就可以很容易的解释 'ascii' codec can't encode character 这个错误的缘由了。

用示例来解释 'ascii' codec can't encode character

In [117]:han = '汉'
         print type(han)
         print len(han)
         str(han) 
         
Out[117]:<type 'str'>
         3              <- '汉'的长度是3,明明是一个字,为什么长度是3?
         '\xe6\xb1\x89' <- 答案在这里

当'汉'这个字被存储在内存中时,它会被转为三个字符'\xe6\xb1\x89'。所以len()给出的长度是3,而不是1. 那么为了让'汉'变成一个真正的字,我们就需要对它进行Decoding。(参看2. 把 0 和 1 转换为Unicode的过程叫Decoding)

In [125]:str.decode(han)
---------------------------------------------------------------------------
UnicodeDecodeError                        Traceback (most recent call last)
<ipython-input-125-3ff96a3a19da> in <module>()
----> 1 str.encode(han)

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 0: ordinal not in range(128)

这里Python抛出了异常。因为默认的ASCII编码无法Decode这个文字。因为这个文字的数值已经超过了0 - 127这个范围。所以我们需要使用UTF-8编码来Decode:

In [127]:str.decode(han, 'utf8')
Out[127]:u'\u6c49'

这里han这个变量被成功Decode为 u'\u6c49

In [142]:uni_han = u'\u6c49'
         len(uni_han)
Out[142]:1              <- 长度变为了正确的1

再来个示例作为结尾

猜猜这段代码的输出是什么:

uni_han = u'\u6c49'
print '\'{0}\'的长度是{1}'.format(uni_han, len(uni_han))

结果是:

---------------------------------------------------------------------------
UnicodeEncodeError                        Traceback (most recent call last)
<ipython-input-143-9bec6fa25583> in <module>()
      1 uni_han = u'\u6c49'
----> 2 print '\'{0}\'的长度是{1}'.format(uni_han, len(uni_han))

UnicodeEncodeError: 'ascii' codec can't encode character u'\u6c49' in position 0: ordinal not in range(128)

好伤心啊,本以为再也不会碰到这个问题了。那么问题出在哪呢?这部分代码'\'{0}\'的长度是{1}'是str,也就是原始的字符数据。我们想把一个Unicode (uni_han)混到它们里一起打印。这时,Python信心满满的用了默认的ASCII编码来Encode uni_han。结果可想而知,又是再次超出0 - 127的范围,无法Encode。这时,我们就需要告诉Python放弃ASCII吧,请使用UTF-8:

In [145]:uni_han = u'\u6c49'
         print '\'{0}\'的长度是{1}'.format(unicode.encode(uni_han,'utf8'), len(uni_han))
         '汉'的长度是1

另一种方法是让前半部分的str变为Unicode:

In [150]:uni_han = u'\u6c49'
         print u'\'{0}\'的长度是{1}'.format(uni_han, len(uni_han))
         '汉'的长度是1

总结

在Python 2.x里str就是原始的010101, Unicode是Unicode,这两个东西不能混着用。当一个文字被写到硬盘上时,或者打印到屏幕上时,需要使用正确的Encoding编码。Python默认使用ASCII,但其实应该用UTF-8。这个问题以后还会经常碰到。关键是要理解ASCII,UTF8,Unicode, Encoding和Decoding的定义和关系。

← 安装 iPython Notebook 在Mac OSX 上安装PostgreSQL和psycopg2 →
 
comments powered by Disqus