(转)python2中的ascii code can't encode/decode错误


本文转自:http://cheng.logdown.com/posts/2015/01/15/ascii-codec-cant-encode-decode-errors-in-python

作者: CHENG

注意: 本文加入了个人的一些理解,由于水平有限可能会有部分错误,欢迎指正。另由于本文仅仅作为个人mark,排版也不美观,所以建议到原文查看!

准备知识:

  1 字符集,字符集实际上就是字符与数字之间的相互映射, 比如unicode字符集  ASCII字符集  GB2312字符集等等,

  2 字符编码,仅仅有字符集是不够的,因为实际工作中我们需要将某些字保存在计算机上,计算机上保存的数据只能是0以及1的排列,还是拿字符集unicode来说,如果我们要将字符保存在计算机上那么就必须有某种规则将unicode字符转换为0以及1,同时这种规则还能让计算机区分出相邻字符的分隔位置,utf32就是一种规则,简单粗暴的用4个字节表示一个字符,以为unicode中最大的数远远小于2的32次方,所以这个规则保证了转码的可靠性,但是对于那些只使用英文的国家来说一个字符就要4字节有点浪费空间啊有木有!于是出现了utf8,可变长的且适用于unicode的一种编码方式,这种方式有效的降低了空间浪费。

用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

在Python2中把一个Unicode类转化为 0 和 1 的过程叫做Encoding。 把 0 和 1 反转为Unicode类的过程叫做Decoding。Python3已经没有unicode和str的区别了,取而代之的是byte和str

在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的定义和关系。

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM