python编码问题(2)
1 编码、解码
如同密码领域一样,从明文到密码是加密,从密码到明文是解密。在python中,(编码:unicode-->str;) 解码(str-->unicode).
既然是编码,那么就和密码领域一样,编码和解码自然涉及到编码/解码方案(对应加密或者解密算法),unicode相当于明文。在python中,编码函数是encode(),解码函数是decode()。需要注意的一点是,如果我们调用str.encode(),这里涉及到一个隐士的类型转化,会现将str转化成unicode,才能进行编码,这也是不太容易理解的地方。所以,str.encode()实际上就等价于str.decode(sys.defaultencoding).encode().而sys.defaultencoding一般是ascii,它是不能用来编码中文字符的。 字符串在Python内部的表示是unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码,即先将其他编码的字符串解码(decode)成unicode,再从unicode编码(encode)成另一种编码。
decode的作用是将其他编码的字符串转换成unicode编码,如str1.decode('gb2312'),表示将gb2312编码的字符串str1转换成unicode编码。encode的作用是将unicode编码转换成其他编码的字符串,如str2.encode('gb2312'),表示将unicode编码的字符串str2转换成gb2312编码。因此,转码的时候一定要先搞明白,字符串str是什么编码,然后decode成unicode,然后再encode成其他编码。代码中字符串的默认编码与代码文件本身的编码一致。
2 字符串
在Python中有两种默认的字符串:str和Unicode:
- str字符串本质上是一个字节流,是原字符经过编码之后的一个个字节,但它并不存储具体的编码方式。
- Unicode字符串本质上是一个Unicode对象,它内部使用了特定的编码,但是对程序员透明。事实上Python中并没有Unicode字符串,但为了理解方便,以下所说的Unicode字符串均指的是Unicode对象。
# -*- coding: utf-8 -*- # file: example1.py import string # 这个是 str 的字符串 s = '关关雎鸠' # 这个是 unicode 的字符串 u = u'关关雎鸠' print isinstance(s, str) # True print isinstance(u, unicode) # True print s.__class__ # <type 'str'> print u.__class__ # <type 'unicode'>
如1中所说,两个 Python 字符串类型间可以用 encode / decode 方法转换
# 从 str 转换成 unicode print s.decode('utf-8') # 关关雎鸠 # 从 unicode 转换成 str print u.encode('utf-8') # 关关雎鸠
如果用错误的字符集来 encode/decode 会怎样?
# 用 ascii 编码含中文的 unicode 字符串 u.encode('ascii') # 错误,因为中文无法用 ascii 字符集编码 # UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128) # 用 gbk 编码含中文的 unicode 字符串 u.encode('gbk') # 正确,因为 '关关雎鸠' 可以用中文 gbk 字符集表示 # '\xb9\xd8\xb9\xd8\xf6\xc2\xf0\xaf' # 直接 print 上面的 str 会显示乱码,修改环境变量为 zh_CN.GBK 可以看到结果是对的 # 用 ascii 解码 utf-8 字符串 s.decode('ascii') # 错误,中文 utf-8 字符无法用 ascii 解码 # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128) # 用 gbk 解码 utf-8 字符串 s.decode('gbk') # 不出错,但是用 gbk 解码 utf-8 字符流的结果,显然只是乱码 # u'\u934f\u51b2\u53e7\u95c6\u5ea8\u7b2d'
几个小问题
(1) 字符串拼接
# -*- coding: utf-8 -*- # file: example2.py # 这个是 str 的字符串 s = '关关雎鸠' # 这个是 unicode 的字符串 u = u'关关雎鸠' s + u # 失败,UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)
在进行同时包含 str 与 unicode 的运算时,Python 一律都把 str 转换成 unicode 再运算,当然,运算结果也都是 unicode。
由于 Python 事先并不知道 str 的编码,它只能使用 sys.getdefaultencoding() 编码去 decode。在我的印象里,sys.getdefaultencoding() 的值总是 'ascii' ——显然,如果需要转换的 str 有中文,一定会出现错误。
(2)字符串长度
str类型的字符串长度为其编码后的长度,并不是字符串本身长度。Unicode字符串的类型为"unicode",使用len得到的长度为2,表示了这个字符串的真正长度。
>>> s = '你好' >>> type(s) <type 'str'> >>> s '\xc4\xe3\xba\xc3' >>> print s 你好 >>> len(s) 4
>>> u = u'你好' >>> type(u) <type 'unicode'> >>> u u'\u4f60\u597d' >>> print u 你好 >>> len(u) 2
为了减少不必要的麻烦,在实际的编程工作中,我们可以按如下方法进行:
- 最先解码。首先要将输入的字符字节流解码为Unicode对象,方便后续如计数、切片等操作的进行。
- 最后编码。最后将处理后的结果输出到其他文件或者数据库的时候才将Unicode对象进行编码,以满足特定的存储显示需求。
- 默认使用utf-8编码。使用UTF-8编码可以处理任何Unicode字符,具有更好地通用性,尽量用来替代默认的ASCII或者GBK编码,但是要注意Windows中默认编码为GBK或者其他类似GBK的编码的情况
3 文件编码
文件编码指的是具体的代码文本在保存时使用的编码方法。如:s='中文',如果是在utf8的文件中,该字符串就是utf8编码,如果是在gb2312的文件中,则其编码为gb2312。
如果声明的编码(指-*- coding: UTF-8 -*-这样的申明)和文件实际的编码不一致的话,很可能会出现乱码甚至出错等问题。原因是对于类似于u’你好’这样声明的字符串,在源代码文件里经过了该文件指定编码方法的编码被保存成字节流,程序运行时读取到这个字节流,就会使用编码声明中的编码去解码得到原始字符串,然后对这个原始字符串再用Unicode的内部编码方案进行存储。而对于’你好’这样不带u的字符串,则表示字节流,此时会直接用文件编码后的字节数组进行表示,不需要使用编码声明中的编码方案进行解码。例如,建立如下内容的脚本文件然后保存成ANSI编码:
# -*- coding: UTF-8 -*- s = '你好' print repr(s), s u = u'你好' print repr(u), u
运行后得到错误
SyntaxError: (unicode error) ‘utf8′ codec can’t decode byte 0xc4 in position 0: invalid continuation byte
但把脚本改成:
# -*- coding: UTF-8 -*- s = '你好' print repr(s), s
则可以得到以下结果:
'\xc4\xe3\xba\xc3' 你好 #原文件使用的是ANSI编码,所以得到GBK编码,GBK编码可以正常显示
参考资料
http://in355hz.iteye.com/blog/1860787
http://5319188.blog.51cto.com/5309188/1112505
http://noalgo.info/578.html