问题
最近在用python 2.7写代码的时候,遇到一个老生常谈的小坑----- ** 字符编码 **
import MySQLdb
conn = MySQLdb.connect(host=..., user=..., passwd=..., db=..., charset='utf8', port=...) //此处已设置charset为utf8
从数据库获取到的数据是:value = '\u6295\u8d44。。。。。' ** 注意:前面没有u **
然后在django后端views.py中,
from django.http import JsonResponse
用JsonResponse(value)返回数据,前端进行渲染,结果在页面上不显示返回结果。
排查过程
- 打开chrome的console调试,发现报错500--Internal Server Error。。。
- 查看log发现出现两种报错:'utf8' codec can't decode bytes in position 3-4: invalid continuation byte 和 'ascii' codec can't decode byte 0xe4 in position 0
- views.py的开头,已经指定了utf-8显示:
# -*- encoding: utf-8 -*-
importsyswufazhengquejiexi
reload(sys)
sys.setdefaultencoding('utf-8')
调试(工具为ipython)
查看变量value的类型,可见value并不是unicode类型,而是str类型
分析
首先我们看下在python中,unicode 和 utf-8/gb2312/ascii 的区别 (参照百度百科):
- unicode :在python中是一个类,函数unicode(str,"utf8"),将utf8编码(或其他编码)的字符串str转换为unicode类的对象。它表示一个变量的类型,可以用isinstance(value, unicode)来查看value变量是否为unicode格式的字符。
- ascii :是一种字符集,包括大小写的英文字母、数字、控制字符等,翻译为美国信息互换标准代码,是美国国家标准学会(ANSI)制定的英文编码。
- gb2312:国标码,是对 ASCII 的中文扩展
- utf-8:是一种针对unicode的可变长度字符编码,它是在互联网上使用最广的一种unicode的实现方式,是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了
- gb2312和utf-8都是按照unicode规定的方式,用不同的编码排列方式,将字符与二进制的01进行对应的表。
然后我们研究下python中的encode和decode函数:
unicode_1 = u'中文'
str_1 = unicode_1.encode('gb2312')
将unicode对象unicode_1用gb2312编码,得到str类型变量str_1,结果见图:
unicode_2 = str_1.decode('gb2312')
用gb2312编码对字符串str_gb进行解码,得到unicode类型对象,结果见图:
decode()和encode()关系如图:
解决办法
现在回到我们的问题中,查看了一下mysql数据库,发现里面存储的数据确实是utf-8格式,因此用网上搜到的方法
value = value.decode('gb2312').encode('utf-8')
来修改代码,前端仍然不显示。这里是因为数据库里存储的并非gb2312格式的数据,所以decode是没用的。
再查,发现产生 ‘XXX' codec can't decode bytes in position XX 这种错误信息的真正原因,竟然是decode过程中遇到了非法字符,因此无法正确解码(参照:blog.csdn.net/cnmilan/article/details/9264643)。
而在做decode时,加上ignore参数,则会忽略非法字符:
value = value.decode('utf-8', 'ignore').encode('utf-8')
至此,问题得到解决。
总结
一定要真正理解unicode和utf-8的区别,还有要梳理清楚decode()和encode()的关系,排查出问题的真正原因,而不是随便在网上搜索模糊的答案,一来未必能真正解决问题,二来永远不会进步!