1. 基本概念
字符集(Character set)
解释: 文字和符合的总称
常见字符集:
Unicode 字符集
ASCII 字符集(Unicode 子集)
GB2312 字符集
编码方法(Encoding)
解释: 将字符对应到字节的方法, 部分字符集和编码方法名称一样.
常见编码方法:
UTF-8: 可对 Unicode 字符进行编码
GB2312
ASCII
编码(Encode)
解释: 将字符集中字符按照一定规则转换成字节
解码(Decode)
解释: 与编码相反, 将字节转换为字符集中的字符
字符集与编码方法的关系
每个字符集都有对应的编码方法
一种字符集可能有多种编码方法
不同的编码方法得到的字节不同, 占用存储空间也不一样
例如 Unicode 字符可以使用 UTF-8/ASCII/GBK 等方法编码
Unicode 字符集包含世界上大部分字符, 很多其他字符集有的字符它都有, 是他们的超集
大部分字符集可以理解为 Unicode 的子集
实际上, 除了 Unicode 之外所谓的字符集主要是对 Unicode 部分字符编码而已(定义编码方式)
一种编码不必支持 Unicode 的所有字符(通常把它能支持的那部分称为它的字符集)
2. 关于编码的错误和解决方法
在开发过程中, 我们所接触的字符集大多都是 Unicode, 大部分报错都是关于编码和解码的.
2.1. 编码错误 UnicodeEncodeError
2.1.1. 错误分析
导致该错误的原因通常是编码方法支持的 Unicode 字符不全; 在工作中, 你写了一个 txt 中文文档, 想用 ascii 编码去保存这个文件, 就会报这种错误.
错误复现:
我们知道 ascii 不支持字符中, 那我们用 ascii 编码方法对 Unicode 码中进行编码:
- # -*- encoding: utf-8 -*-
- u"中".encode(encoding='ascii')
报错如下:
UnicodeEncodeError: 'ascii' codec can't encode character'\u4e2d' in position 0: ordinal not in range(128)
这是一个 UnicodeEncodeError 类型的错误, 提示无法使用指定的编码方法对字符进行编码, 报错提示中可以得到 3 个信息:
当前使用的是 acsii 编码方法
被编码的字符是'\u4e2d'
ascii 编码方法能支持的字符有 128 个
有时候我们还可以利用这个提示查看编码方法支持的字符个数:
- # -*- encoding: utf-8 -*-
- u"中".encode(encoding='iso-8859-1')
报错:
UnicodeEncodeError: 'latin-1' codec can't encode character'\u4e2d' in position 0: ordinal not in range(256)
通过报错提示可以看出 iso-8859-1 能编 256 个字符.
接着, 我们来看下用支持中文的 utf-8 编码方法进行编码能得到什么结果:
- # -*- encoding: utf-8 -*-
- s = u"中".encode(encoding='utf-8')
- print("s:", s)
- print("s == 中?" , s == '中')
- print("type of s:", type(s))
- print("str==bytes?", bytes == type(s))
输出:
- ('s:', '\xe4\xb8\xad')
- ('s == \xe4\xb8\xad?', True)
- ('type of s:', <type 'str'>)
- ('str==bytes?', True)
从输出的结果可以得到:
- 编码得到的对象跟我们直接定义的字符串是一样的, 都是 str
- `str` 就是 `bytes`(python 中)
2.1.2. 解决方法
UnicodeEncodeError 是说编码方法支持的字符不全, 而 UTF-8 编码就能很好地对 Unicode 编码, 所以只要把编码方法指定为 utf-8 就可以了.
在 python2 中:
如果你调用 encode 方法但没有指定 encoding 参数, 那很可能使用了系统默认的参数, 就像:
- # -*- encoding: utf-8 -*-
- import sys
- print "default encoding is %s ." % sys.getdefaultencoding()
- u'中'.encode()
输出:
- default encoding is ascii .
- UnicodeEncodeError: 'ascii' codec can't encode character u'\u4e2d' in position 0: ordinal not in range(128)
可以手动指定 encoding 参数, 也可以修改 python 默认编码方法:
- # -*- encoding: utf-8 -*-
- import sys
- reload(sys)
- sys.setdefaultencoding('utf-8') # 必须在 reload 之后调用
- print u'中'.encode()
在 python3 中:
在 python3 中你很难看到 UnicodeEncodeError 了, 因为 python3 的默认编码就是 utf-8, 而 Unicode 字符都可以用 utf-8 编码方法编码.
2.2. 编码错误 UnicodeDecodeError
2.2.1. 错误分析
导致该错误的原因是使用了错误的解码方式把字节数据还原成字符. 例如在工作中, 有一个 utf-8 生成中文文档, 我们选择用 ascii 编码解码, 就会报这个错.
错误复现:
我们知道 python 中字符串和字节是一样, 我们可以定义一个中文字符串, 通过 ascii 来解码生成 Unicode, 复现这个错误:
- # -*- encoding: utf-8 -*-
- print '中'.decode(encoding='ascii')
输出:
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
注意, 这是一个 UnicodeDecodeError 错误, 区别于编码错误 UnicodeEncodeError, 它提示无法把字节 0xe4(实际上中对应三字节, 0xe4 是第一个) 解码成 Unicode.0xe4 转换为 10 进制是 228, 已经超过 127 了, 所以 ascii 无能力了.
2.2.2. 解决方法
我们需要知道生成字节的编码方式才能进行还原, 就好像对一个文件进行了加密, 必须得知道加密的密码才能把文件还原, 而解码方式 (或者称为编码) 就是那个密码. 所以不管是 python2 还是 python3 直接去修改默认编码为 UTF-8 不一定能解决问题, 具体的方法有两种:
通过源码找到解码失败的字节是使用那种编码生成的
对报错的字节数据使用各种常见的编码进行解码, 观察哪一种是正确的
以一个例子来说明为什么直接设置默认编码为 UTF-8 不能有效解决 UnicodeDecodeError 问题
python 文件头指定了编码为, 在声明字符串时候将会使用指定的编码转换为字节:
- # -*- encoding: GBK -*-
- s = "张"
- print ("s is", s)
- s_unicode = u'张'
- print ("encoding with GBK is", s_unicode.encode("GBK"))
- print ("encoding with UTF-8 is", s_unicode.encode("UTF-8"))
输出:
- ('s is', '\xd5\xc5')
- ('encoding with GBK is', '\xd5\xc5')
- ('encoding with UTF-8 is', '\xe5\xbc\xa0')
在文件头中指定了 GBK 编码后默认情况下字符张就会被编码为 \ xd5\xc5, 这与我们手动用 GBK 编码得到结果一致, 而使用 utf-8 编码得到的是 3 个字节的数据 \ xe5\xbc\xa0(使用了更多的存储空间).
例子开始了, 在 python2 中将一个 dict 转换成 JSON:
- # -*- encoding: GBK -*-
- import JSON
- d = {
- 'name': "张", 'sex': u'man'
- }
- print (JSON.dumps(d))
输出:
UnicodeDecodeError: 'utf8' codec can't decode byte 0xd5 in position 0: invalid continuation byte
错误说无法使用 utf-8 解码 0xd5,0xd5 也就是 GBK 中的张, 我们知道这个字节是用 GBK 生成的, 这个时候可以设置 JSON.dumps 的 encoding 参数解决:
- # -*- encoding: GBK -*-
- import JSON
- d = {
- 'name': "张", 'sex': u'man'
- }
- print (JSON.dumps(d, encoding='gbk'))
修改一下代码, 继续使用我们熟悉的 utf-8 编码来执行:
- # -*- encoding: utf-8 -*-
- import JSON
- d = {
- 'name': "张", 'sex': u'man'
- }
- print (JSON.dumps(d, encoding='utf-8'))
输出:
{"name": "\u5f20", "sex": "man"}
发现 name 是 unicode, 使用 ensure_ascii=False, 不强制转换成 ascii:
- # -*- encoding: utf-8 -*-
- import JSON
- d = {
- 'name': "张", 'sex': u'man'
- }
- print (JSON.dumps(d, encoding='utf-8', ensure_ascii=False))
输出:
- File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.py", line 251, in dumps
- sort_keys=sort_keys, **kw).encode(obj)
- File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.py", line 210, in encode
- return ''.join(chunks)
- UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 1: ordinal not in range(128)
纳尼, 文件头也是 utf-8, 也指定了 utf-8 怎么还是报错? 错误提示用 ascii 去解码 0xe5 字节, 在上面的代码输出中可以知道 0xe5 是 utf-8 对字符张编码的第一个字节 , 报错的原因是用 ascii 去解析 utf-8 生成的字节了. 我们并没有设置哪个地方使用 ascii 解码, 应该是系统默认的编码, 尝试设置系统默认编码再执行:
- # -*- encoding: utf-8 -*-
- import sys
- reload(sys)
- sys.setdefaultencoding('utf-8')
- import JSON
- d = {
- 'name': "张", 'sex': u'man'
- }
- print (JSON.dumps(d, encoding='utf-8', ensure_ascii=False))
输出:
{"name": "张", "sex": "man"}
3. 总结
3.1. python2 和 python3 对字符串的处理区别
Python2
默认编码是 ascii
Python3
默认编码为 utf-8
不能使用
import sys;sys.setdefaultencoding('utf-8')
设置默认编码
Unicode 和 str 都用字符串表示了
3.2. 为什么不全用 UTF-8 编码?
UTF-8 包含的字符更多, 占用的内存和磁盘空间也更大, 比如对汉字张, utf-8 是 3 个字节, gbk 是 2 个字节.
3.3. 如何快速解决 UnicodeEncodeError 错误?
python3 中基本不会出现, python2 中尝试设置默认编码为 utf-8.
3.4. 快速解决 UnicodeDecodeError?
需要知道出错的字节是使用哪种编码方式生成的, 然后尝试把默认编码设置成这种.
来源: https://www.cnblogs.com/oaks/p/12776124.html