关于python中的编码问题汇总
2018-02-02 20:47
513 查看
Python的编码问题基本是每个新手都会遇到的坎,但只要完全掌握了就跳过了这个坑,万变不离其中,这不最近我也遇到了这个问题,来一起看看吧。
事情的起因是review同事做的一个上传功能,看下面一段代码,self.fp是上传的文件句柄
这段代码暴露了2个问题
1.默认编码使用gbk,为什么不用utf8?
2..encode(‘utf-8’).decode(‘utf-8’)完全没有必要,decode(‘gbk’)之后就已经是unicode了
我建议上传文本编码为utf8,于是代码变成这样?
可测试时报UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xe4 in position 0: ordinal not in range(128),这个异常估计新手看到很头疼,这是什么意思呢?
就是说:在将ascii字符串decode为unicode时碰到了oxe4这个比特,这不在ascii的范围内,所有decode错误。
交代一下项目背景,我们用Python2.7,这是django工程,self.game是unicode对象,明白人一看就是sys.setdefaultencoding的问题,其实就是下面这个问题
哎呀,可我明明在settings.py中设置默认编码了呀,查看了一下,才发现闹了个笑话
看看到底发生了什么?由于fpdata都是utf8字符串,而self.game是unicode对象,字符串join时会将utf8字符串解码为unicode对象,但系统不知道你是utf8编码,默认使用ascii去解码,这出错也就不足为奇了。
这也是为什么在碰到编码问题时,老手建议增加sys.setdefaultencoding(“utf8”)操作,但为什么要加这么一个操作呢?我们从底层看看发生了什么?
当字符串连接unicode对象时,也就是a+b时,会调用PyString_Concat
如果检测到b是unicode对象,会调用PyUnicode_Concat
由于a不是unicode对象会调用PyUnicode_FromEncodedObject将a转换为unicode对象,传递的编码是NULL
我们看到当encoding是NULL时,encoding是PyUnicode_GetDefaultEncoding(),其实这个就是我们sys.getdefaultencoding()的返回值,Python默认就是ascii
这里unicode_default_encoding是个静态变量,且分配了足够的空间让你指定不同的编码,估计100个字符肯定是够了
我们在看看sys模块的getdefaultencoding和setdefaultencoding
PyUnicode_SetDefaultEncoding不用想也知道设置unicode_default_encoding数组就可以了,Python用的是strncpy
之前我们在sys.setdefaultencoding(“utf8”)时是reload(sys)的,这是因为在Python site.py中有这么一个操作
当然你完全可以定制site.py,修改setencoding,使用locale的设置,也就是将if 0修改为if 1。一般windows的设置locale编码为cp936,服务器一般都是utf8
所以Python的编码是不难的,
要想玩转Python的编码你需要知道
1.unicode与utf8,gbk的区别,以及unicode与具体编码的转换
2.字符串与unicode连接时会转换为unicode, str(unicode)会转换为字符串
3.当不知道具体编码会使用系统默认编码ascii,可通过sys.setdefaultencoding修改
如果能解释下面现象应该就能玩转Python让人讨厌的编码问题
当然这里面还有坑,比如经常在群里看到已经sys.setdefaultencoding(“utf8”)了,但print时候还是报错,这个其实是print造成的,print会根据sys.stdout.encoding再转一次码。
解决办法:
当然也听到不少朋友建议不要用sys.setdefaultencoding,这会让你遇到不少坑,对这点我是持保留意见的,军刀在手就看各位怎么用了,是杀敌1000自损800还是所向披靡。
当然这的确有坑,比如a==b,c是一个字典,c[a]可能并不等于c,因为==会转换编码,但字典并不会。
这里还要提一下下面这个东西https://docs.python.org/2/library/codecs.html
unicode_eacape就是提供unicode和string的转换
我们看到a已经是unicode表示了,但我们需要的是python中unicode字符,所以decode(‘raw_unicode_escape’),还比如写接口返回的json字符串
string_escape是提供string和python中字符串字面量之间的转换。
[b]简要概括一下:
上面说了如此之多,其实,在我们的基本使用中,只需要了解如下知识点:
将python看成是一根管子,管子里头处理的中间过程都是使用unicode的。入口处,全部转成unicode;出口处,再转成目标编码(当然,有例外,处理逻辑中要用到具体编码的情况)。
在控制台执行命令a = u’中文’,可以将解释为命令,a = ‘中文’.decode(encode),从而到到unicode对象a。那么这里的encode是什么呢?对于控制台来说,就是标准输入,即sys.stdin.encoding
经过测试,python2中的标准输入为‘cp936’,python3中的标准输入为‘utf-8’。
回到最开始的那个问题,之所以出错,主要问题在于没有搞清楚unicode和str的区别,将两者进行了混用。
以上的对象a其实是str,即字节码,若终端是utf-8编码的话,那么a就是用utf-8 encode的字节码。a.encode(‘gbk’) 等价于a.decode(encoding).encode(‘gbk’),即先将字节码解码为unicode字符,然后再encode为字节码。unicode对象作为中转站。那么这里的encoding是什么呢?
默认是ascii,这正是错误为什么报无法用ascii解码的原因
将默认编码改为utf-8,即可。不鼓励对str使用encode方法,因为其中隐式对str进行了解码。decode只对str,encode只对unicode,一切decode/encode都显示指定编码方式。
关于python中bytes,str和unicode的区别,这里给出两个式子,自行揣摩:
在Python2中,作为两种类型的字符序列,str与unicode需要转换,它们是这样转换的.
str——decode方法——》unicode——encode方法——》str
在python3中可以这样对应这转换,配合上面的图,也许会好理解一点。
byte——decode(解码)方法——》str——>encode(编码)方法——》byte
最后作一点补充:
如果你不知道你所处理的字符编码类型怎么办呢?其实很简单,见如下代码:
(1)网页编码判断:
(2)文件编码判断
目前我所遇到的,主要就是这些问题了,后面如果遇到其它的再来补充,望各位不吝赐教。
事情的起因是review同事做的一个上传功能,看下面一段代码,self.fp是上传的文件句柄
fpdata = [line.strip().decode('gbk').encode('utf-8').decode('utf-8') for line in self.fp] data = [''.join(['(', self.game, ',', ','.join(map(lambda x: "'%s'" % x, d.split(','))), ')']) for d in fpdata[1:]]
这段代码暴露了2个问题
1.默认编码使用gbk,为什么不用utf8?
2..encode(‘utf-8’).decode(‘utf-8’)完全没有必要,decode(‘gbk’)之后就已经是unicode了
我建议上传文本编码为utf8,于是代码变成这样?
fpdata = [line.strip() for line in self.fp if line.strip()] data = [''.join(['(', self.game, ',', ','.join(map(lambda x: "'%s'" % x, d.split(','))), ')']) for d in fpdata[1:]]
可测试时报UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xe4 in position 0: ordinal not in range(128),这个异常估计新手看到很头疼,这是什么意思呢?
就是说:在将ascii字符串decode为unicode时碰到了oxe4这个比特,这不在ascii的范围内,所有decode错误。
交代一下项目背景,我们用Python2.7,这是django工程,self.game是unicode对象,明白人一看就是sys.setdefaultencoding的问题,其实就是下面这个问题
哎呀,可我明明在settings.py中设置默认编码了呀,查看了一下,才发现闹了个笑话
看看到底发生了什么?由于fpdata都是utf8字符串,而self.game是unicode对象,字符串join时会将utf8字符串解码为unicode对象,但系统不知道你是utf8编码,默认使用ascii去解码,这出错也就不足为奇了。
这也是为什么在碰到编码问题时,老手建议增加sys.setdefaultencoding(“utf8”)操作,但为什么要加这么一个操作呢?我们从底层看看发生了什么?
当字符串连接unicode对象时,也就是a+b时,会调用PyString_Concat
# stringobject.c void PyString_Concat(register PyObject **pv, register PyObject *w) { register PyObject *v; if (*pv == NULL) return; if (w == NULL || !PyString_Check(*pv)) { Py_CLEAR(*pv); return; } v = string_concat((PyStringObject *) *pv, w); Py_DECREF(*pv); *pv = v; } static PyObject * string_concat(register PyStringObject *a, register PyObject *bb) { register Py_ssize_t size; register PyStringObject *op; if (!PyString_Check(bb)) { if (PyUnicode_Check(bb)) return PyUnicode_Concat((PyObject *)a, bb); if (PyByteArray_Check(bb)) return PyByteArray_Concat((PyObject *)a, bb); PyErr_Format(PyExc_TypeError, "cannot concatenate 'str' and '%.200s' objects", Py_TYPE(bb)->tp_name); return NULL; } ... }
如果检测到b是unicode对象,会调用PyUnicode_Concat
PyObject *PyUnicode_Concat(PyObject *left, PyObject *right) { PyUnicodeObject *u = NULL, *v = NULL, *w; /* Coerce the two arguments */ u = (PyUnicodeObject *)PyUnicode_FromObject(left); v = (PyUnicodeObject *)PyUnicode_FromObject(right); w = _PyUnicode_New(u->length + v->length); Py_DECREF(v); return (PyObject *)w; } PyObject *PyUnicode_FromObject(register PyObject *obj) { if (PyUnicode_Check(obj)) { /* For a Unicode subtype that's not a Unicode object, return a true Unicode object with the same data. */ return PyUnicode_FromUnicode(PyUnicode_AS_UNICODE(obj), PyUnicode_GET_SIZE(obj)); } return PyUnicode_FromEncodedObject(obj, NULL, "strict"); }
由于a不是unicode对象会调用PyUnicode_FromEncodedObject将a转换为unicode对象,传递的编码是NULL
PyObject *PyUnicode_FromEncodedObject(register PyObject *obj, const char *encoding, const char *errors) { const char *s = NULL; Py_ssize_t len; PyObject *v; /* Coerce object */ if (PyString_Check(obj)) { s = PyString_AS_STRING(obj); len = PyString_GET_SIZE(obj); } /* Convert to Unicode */ v = PyUnicode_Decode(s, len, encoding, errors); return v; } PyObject *PyUnicode_Decode(const char *s, Py_ssize_t size, const char *encoding, const char *errors) { PyObject *buffer = NULL, *unicode; if (encoding == NULL) encoding = PyUnicode_GetDefaultEncoding(); /* Shortcuts for common default encodings */ if (strcmp(encoding, "utf-8") == 0) return PyUnicode_DecodeUTF8(s, size, errors); else if (strcmp(encoding, "latin-1") == 0) return PyUnicode_DecodeLatin1(s, size, errors); else if (strcmp(encoding, "ascii") == 0) return PyUnicode_DecodeASCII(s, size, errors); /* Decode via the codec registry */ buffer = PyBuffer_FromMemory((void *)s, size); if (buffer == NULL) goto onError; unicode = PyCodec_Decode(buffer, encoding, errors); return unicode; }
我们看到当encoding是NULL时,encoding是PyUnicode_GetDefaultEncoding(),其实这个就是我们sys.getdefaultencoding()的返回值,Python默认就是ascii
static char unicode_default_encoding[100 + 1] = "ascii"; const char *PyUnicode_GetDefaultEncoding(void) { return unicode_default_encoding; }
这里unicode_default_encoding是个静态变量,且分配了足够的空间让你指定不同的编码,估计100个字符肯定是够了
我们在看看sys模块的getdefaultencoding和setdefaultencoding
static PyObject * sys_getdefaultencoding(PyObject *self) { return PyString_FromString(PyUnicode_GetDefaultEncoding()); } static PyObject * sys_setdefaultencoding(PyObject *self, PyObject *args) { if (PyUnicode_SetDefaultEncoding(encoding)) return NULL; Py_INCREF(Py_None); return Py_None; }
PyUnicode_SetDefaultEncoding不用想也知道设置unicode_default_encoding数组就可以了,Python用的是strncpy
int PyUnicode_SetDefaultEncoding(const char *encoding) { PyObject *v; /* Make sure the encoding is valid. As side effect, this also loads the encoding into the codec registry cache. */ v = _PyCodec_Lookup(encoding); if (v == NULL) goto onError; Py_DECREF(v); strncpy(unicode_default_encoding, encoding, sizeof(unicode_default_encoding) - 1); return 0; onError: return -1; }
之前我们在sys.setdefaultencoding(“utf8”)时是reload(sys)的,这是因为在Python site.py中有这么一个操作
if hasattr(sys, "setdefaultencoding"): del sys.setdefaultencoding
当然你完全可以定制site.py,修改setencoding,使用locale的设置,也就是将if 0修改为if 1。一般windows的设置locale编码为cp936,服务器一般都是utf8
def setencoding(): """Set the string encoding used by the Unicode implementation. The default is 'ascii', but if you're willing to experiment, you can change this.""" encoding = "ascii" # Default value set by _PyUnicode_Init() if 0: # Enable to support locale aware default string encodings. import locale loc = locale.getdefaultlocale() if loc[1]: encoding = loc[1] if 0: # Enable to switch off string to Unicode coercion and implicit # Unicode to string conversion. encoding = "undefined" if encoding != "ascii": # On Non-Unicode builds this will raise an AttributeError... sys.setdefaultencoding(encoding) # Needs Python Unicode build !
所以Python的编码是不难的,
要想玩转Python的编码你需要知道
1.unicode与utf8,gbk的区别,以及unicode与具体编码的转换
2.字符串与unicode连接时会转换为unicode, str(unicode)会转换为字符串
3.当不知道具体编码会使用系统默认编码ascii,可通过sys.setdefaultencoding修改
如果能解释下面现象应该就能玩转Python让人讨厌的编码问题
当然这里面还有坑,比如经常在群里看到已经sys.setdefaultencoding(“utf8”)了,但print时候还是报错,这个其实是print造成的,print会根据sys.stdout.encoding再转一次码。
解决办法:
export LANG="en_US.UTF-8"
当然也听到不少朋友建议不要用sys.setdefaultencoding,这会让你遇到不少坑,对这点我是持保留意见的,军刀在手就看各位怎么用了,是杀敌1000自损800还是所向披靡。
当然这的确有坑,比如a==b,c是一个字典,c[a]可能并不等于c,因为==会转换编码,但字典并不会。
这里还要提一下下面这个东西https://docs.python.org/2/library/codecs.html
unicode_eacape就是提供unicode和string的转换
我们看到a已经是unicode表示了,但我们需要的是python中unicode字符,所以decode(‘raw_unicode_escape’),还比如写接口返回的json字符串
string_escape是提供string和python中字符串字面量之间的转换。
[b]简要概括一下:
上面说了如此之多,其实,在我们的基本使用中,只需要了解如下知识点:
将python看成是一根管子,管子里头处理的中间过程都是使用unicode的。入口处,全部转成unicode;出口处,再转成目标编码(当然,有例外,处理逻辑中要用到具体编码的情况)。
在控制台执行命令a = u’中文’,可以将解释为命令,a = ‘中文’.decode(encode),从而到到unicode对象a。那么这里的encode是什么呢?对于控制台来说,就是标准输入,即sys.stdin.encoding
>>> sys.stdin.encoding 'cp936'
经过测试,python2中的标准输入为‘cp936’,python3中的标准输入为‘utf-8’。
回到最开始的那个问题,之所以出错,主要问题在于没有搞清楚unicode和str的区别,将两者进行了混用。
>>> a = '中文' >>> a.encode('gbk') Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
以上的对象a其实是str,即字节码,若终端是utf-8编码的话,那么a就是用utf-8 encode的字节码。a.encode(‘gbk’) 等价于a.decode(encoding).encode(‘gbk’),即先将字节码解码为unicode字符,然后再encode为字节码。unicode对象作为中转站。那么这里的encoding是什么呢?
>>> import sys >>> sys.getdefaultencoding() 'ascii'
默认是ascii,这正是错误为什么报无法用ascii解码的原因
>>> reload(sys) <module 'sys' (built-in)> >>> sys.setdefaultencoding('utf-8') >>> a = '中文' >>> repr(a) "'\\xe4\\xb8\\xad\\xe6\\x96\\x87'" >>> a.encode('gbk') '\xd6\xd0\xce\xc4'
将默认编码改为utf-8,即可。不鼓励对str使用encode方法,因为其中隐式对str进行了解码。decode只对str,encode只对unicode,一切decode/encode都显示指定编码方式。
关于python中bytes,str和unicode的区别,这里给出两个式子,自行揣摩:
在Python2中,作为两种类型的字符序列,str与unicode需要转换,它们是这样转换的.
str——decode方法——》unicode——encode方法——》str
在python3中可以这样对应这转换,配合上面的图,也许会好理解一点。
byte——decode(解码)方法——》str——>encode(编码)方法——》byte
最后作一点补充:
如果你不知道你所处理的字符编码类型怎么办呢?其实很简单,见如下代码:
(1)网页编码判断:
>>> import urllib >>> rawdata = urllib.urlopen('http://www.google.cn/').read() >>> import chardet >>> chardet.detect(rawdata) {'confidence': 0.98999999999999999, 'encoding': 'GB2312'}
(2)文件编码判断
import chardet tt=open('c:\\111.txt','rb') ff=tt.readline() #这里试着换成read(5)也可以,但是换成readlines()后报错 enc=chardet.detect(ff) print enc['encoding'] tt.close()
目前我所遇到的,主要就是这些问题了,后面如果遇到其它的再来补充,望各位不吝赐教。
相关文章推荐
- 关于python编码问题无法读取GBK文件
- 关于python字符串编码问题的理解
- 关于python3的编码问题
- 关于python3里gbk编码的问题解决
- 关于python中的数组和矩阵的问题汇总
- 关于python中json load出来编码为unicode的问题的解决
- 关于Python2.X与Python3.X的编码问题
- 关于Windows下python编码问题
- 关于Python笔试中提交代码多组测试的输入问题汇总
- 关于python的编码问题
- 关于Python-requests内容编码问题
- 关于python的str和unicode以及编码的问题
- 编程语言的学习 ------ python3 关于编码的转换问题
- 关于python的编码问题的个人小结
- 关于python的编码问题
- 关于python3的一些编码问题
- 关于编码问题的理解(python)
- 关于python安装问题 问题汇总
- 关于Python文档读取UTF-8编码文件问题
- python编码问题汇总