如何理解 python UnicodeEncodeError 和 UnicodeDecodeError :python 的 string 和 unicode

文中 python 皆为 2.x 版本html

初学 python 的人基本上都有过以下相似经历:java

UnicodeDecodeErrorpython

Traceback (most recent call last):
  File "<input>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)复制代码

UnicodeEncodeErrorc++

Traceback (most recent call last):
  File "<input>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)复制代码

这两个错误在 python 中十分常见,一不留神就碰上了。若是你写过c、c++ 或者 java,对比之下必定会以为 python 这个错误真让人火大。事实也确实如此,我也曾经很火大🔥。git

这两个错误究竟意味着什么?能够先从 python 的基本数据类型 string 和 unicode 开始。github

string

字符串(string)其实就是一段文本序列,是由一个或多个字符组成(character),字符是文本的最小构成单元,在 python 中能够用如下方式表示字符串:shell

>>> s1 = 'abc'
>>> s2 = "abc"
>>> s3 = """ abc """
>>> s4 = '中文'
>>> for i in [s1, s2, s3, s4]:
        print type(i)
<type 'str'>
<type 'str'>
<type 'str'>
<type 'str'>复制代码

这些变量在 python shell 中对应输出是:数据库

s1 --> 'abc'
s2 --> 'abc'
s3 --> '\nabc\n'
s4 --> '\xe4\xb8\xad\xe6\x96\x87'复制代码

s4 的输出和其它变量明显不一样,字面上是一个 16 进制序列,可是 s4 和其它字符串同样,在 python 内部都是用一样方式进行存储的: 字节流(byte stream),即字节序列。bash

字节是计算机内部最小的可寻址的存储单位(对大部分计算机而言),一个字节是由 8 bit 组成,也就是对应 8 个二进制位。其实能够更进一步解释说,python 不只用字节的方式存储着变量中的字符串文本,python 文件中的全部信息在计算机内部都是用一个个字节表示的,计算机是用这样的方式存储文本数据的。微信

字符串用字节如何表示?

答案就是编码。计算机是只能识别 0 或 1 这样的二进制信息,而不是 a 或 b 这样对人类有意义的字符,为了让机器能读懂这些字符,人类就发明字符到二进制的映射关系,而后按照这个映射规则进行相应地编码。ascii 就是这样背景下诞生的一种编码规则。ascii 也是 python 2.x 默认使用的编码规则。

ascii 规定了经常使用的字符到计算机是如何映射的,编码范围是 0~127 共 128 个字符。简单来讲它就是一本字典,规定了不一样字符的对应的编码值(code point,一个整数值),这样一来计算机就能用二进制表示了。好比字符 a 的编码是 97,对应的二进制是 1100001,一个字节就足够存储这些信息。字符串 "abc" 最终存储就是 [97] [98] [99] 三个字节。python 默认状况下就是使用这个规则对字符进行编码,对字节进行解码(反编码)。

>>> ord('a')
97
>>> chr(97)
'a'
>>>复制代码

因为 ascii 的编码范围很是有限,对超过 ascii 范围以外的字符,python 是如何处理的?很简单,抛错误出来,这就是 UnicodeEncodeErrorUnicodeDecodeError 的来源。那 python 会在何时抛出这样的错误,也就是说 python 进行编码和解码的操做发生在什么时候?

unicode 对象

unicode 对象和 string 同样,是 python 中的一种字符对象(python 中一切皆对象,string 也是)。先不要去想 unicode 字符集、unicode 编码或者 utf-8 这些概念,在此特地加了对象就是为了和后面提到的 unicode 字符集进行区分。这里说的 unicode 就是 python 中的 unicode 对象,构造函数是 unicode()

在 python 中创造 unicode 对象也很简单:

>>> s1 = unicode('abc')
>>> s2 = u'abc'
>>> s3 = U'abc'
>>> s4 = u'中文'复制代码

这些变量在 python shell 中对应输出是:

s1 --> u'abc'
s2 --> u'abc'
s3 --> u'abc'
s4 --> u'\u4e2d\u6587'复制代码

一样的,s4 的输出和其它变量不一样,这些就是unicode 字符。因为 ascii 可以表示的字符太少,并且不够通用(扩展 ascii 的话题,就是把 ascii 没有利用的剩下大于 127 的位置利用了,在不一样的字符集里表明不一样的意思),unicode 字符集 就被造出来了,一本更大的字典,里面有更多的编码值。

unicode 字符集

unicode 字符集解决了:

  • ascii 表达能力不够的问题
  • 扩展 ascii 不够通用的问题

虽然 unicode 字符集表达能力强,又可以统一字符编码规则,可是它并无规定这些字符在计算机中是如何表示的。它和 ascii 不一样,不少字符(编码值大于 255 )没有办法用一个字节就搞定。怎样作到高效快捷地存储这些编码值?因而就有了 unicode 字符集的编码规则的实现:utf-八、utf-16等。

到这里能够简单理清 ascii、unicode 字符集、utf-8等的关系了:ascii 和 unicode 字符集都是一种编码集,由字符和字符对应的整数值(code point)组成,ascii 在计算机内部用一个字节存储,utf-8 是 unicode 字符集存储的具体实现,由于 unicode 字符集没有办法简简单单用一个字节搞定。

回到 s4 对应的输出,这个输出就是 unicode 字符集对应的编码值(code point)的 16 进制表示。

unicode 对象是用来表示 unicode 字符集中的字符,这些字符(实际是那个编码值,一个整数) 在 python 中又是如何存储的?有了前文的分析,也许能够猜到,python 依然是经过编码而后用字节的方式存储,可是这里的编码就不能是 ascii 了,而是对应 unicode 字符集的编码规则: utf-八、utf-16等。

unicode 对象的编码

unicode 对象想要正确的存储就必须指定相应的编码规则,这里咱们只讨论使用最普遍的 utf-8 实现。

在 python 中对 unicode 对象编码以下:

>>> s=u'中文'
>>> s.encode('utf-8')
'\xe4\xb8\xad\xe6\x96\x87'
>>> type(s.encode('utf-8'))
<type 'str'>复制代码

编码以后输出的是个 string 并以字节序列的方式进行存储。有了编码就会有解码,python 正是在这种编码、解码的过程使用了错误的编码规则而发生了 UnicodeEncodeErrorUnicodeDecodeError 错误,由于它默认使用 ascii 来完成转换。

string 和 unicode 对象的转换

unicode 对象能够用 utf-8 编码为 string,同理,string 也能够用 utf-8 解码为 unicode 对象

>>> u=u'中文'
>>> s = u.encode('utf-8')
>>> s
'\xe4\xb8\xad\xe6\x96\x87'
>>> type(s)
<type 'str'>
>>> s.decode('utf-8')
u'\u4e2d\u6587'
>>> type(s.decode('utf-8'))
<type 'unicode'>复制代码

错误的编码规则就会致使那两个常见的异常

>>> u.encode('ascii')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
>>>
>>> s.decode('ascii')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)复制代码

这两个错误在某些时候会忽然莫名其妙地出现就是由于 python 自动地使用了 ascii 编码。

python 自动解编码

1.stirng 和 unicode 对象合并

>>> s + u''
Traceback (most recent call last):
  File "<input>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
>>>复制代码

2.列表合并

>>> as_list = [u, s]
>>> ''.join(as_list)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)复制代码

3.格式化字符串

>>> '%s-%s'%(s,u)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128) >>>复制代码

4.打印 unicode 对象

#test.py
# -*- coding: utf-8 -*-
u = u'中文'
print u

#outpt
Traceback (most recent call last):
  File "/Users/zhyq0826/workspace/zhyq0826/blog-code/p20161030_python_encoding/uni.py", line 3, in <module>
    print u
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)复制代码

5.输出到文件

>>> f = open('text.txt','w')
>>> f.write(u)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
>>>复制代码

1,2,3 的例子中,python 自动用 ascii 把 string 解码为 unicode 对象而后再进行相应操做,因此都是 decode 错误, 4 和 5 python 自动用 ascii 把 unicode 对象编码为字符串而后输出,因此都是 encode 错误。

只要涉及到 unicode 对象和 string 的转换以及 unicode 对象输出、输入的地方可能都会触发 python 自动进行解码/编码,好比写入数据库、写入到文件、读取 socket 等等。

到此,这两个异常产生的真正缘由了基本已经清楚了: unicode 对象须要编码为相应的 string(字符串)才能够存储、传输、打印,字符串须要解码为对应的 unicode 对象才能完成 unicode 对象的各类操做,lenfind 等。

string.decode('utf-8') --> unicode
unicode.encode('utf-8') --> string复制代码

如何避免这些的错误

1.理解编码或解码的转换方向

不管什么时候发生编码错误,首先要理解编码方向,而后再针对性解决。

2.设置默认编码为 utf-8

在文件头写入

# -*- coding: utf-8 -*-复制代码

python 会查找: coding: name or coding=name,并设置文件编码格式为 name,此方式是告诉 python 默认编码再也不是 ascii ,而是要使用声明的编码格式。

3.输入对象尽早解码为 unicode,输出对象尽早编码为字节流

不管什么时候有字节流输入,都须要尽早解码为 unicode 对象。任什么时候候想要把 unicode 对象写入到文件、数据库、socket 等外界程序,都须要进行编码。

4.使用 codecs 模块来处理输入输出 unicode 对象

codecs 模块能够自动的完成解编码的工做。

>>> import codecs
>>> f = codecs.open('text.txt', 'w', 'utf-8')
>>> f.write(u)
>>> f.close()复制代码

参考文献


wecatch

咱们致力于创造有价值的互联网产品和服务,分享有洞见的观点。

相关文章
相关标签/搜索