对应 C/C++ 的 char 和 wchar_t, Python 也有两种字符串类型,str 与 unicode:
1 # -*- coding: utf-8 -*- 2 # file: example1.py 3 import string 4 5 # 这个是 str 的字符串 6 s = '关关雎鸠' 7 8 # 这个是 unicode 的字符串 9 u = u'关关雎鸠' 10 11 print isinstance(s, str) # True 12 print isinstance(u, unicode) # True 13 14 print s.__class__ # <type 'str'> 15 print u.__class__ # <type 'unicode'>stryu
前面的申明:# -*- coding: utf-8 -*- 表明,上面的 Python 代码由 utf-8 编码。
两个 Python 字符串类型间可以用 encode / decode 方法转换:
1 # 从 str 转换成 unicode 2 print s.decode('utf-8') # 关关雎鸠 3 4 # 从 unicode 转换成 str 5 print u.encode('utf-8') # 关关雎鸠
为什么从 unicode 转 str 是 encode,而反过来叫 decode?
因为 Python 认为 16 位的 unicode 才是字符的唯一内码,而大家常用的字符集如 gb2312,gb18030/gbk,utf-8,以及 ascii 都是字符的二进制(字节)编码形式。把字符从 unicode 转换成二进制编码,当然是要 encode。
反过来,在 Python 中出现的 str 都是用字符集编码的 ansi 字符串。Python 本身并不知道 str 的编码,需要由开发者指定正确的字符集 decode。
(补充一句,其实 Python 是可以知道 str 编码的。因为我们在代码前面申明了 # -*- coding: utf-8 -*-,这表明代码中的 str 都是用 utf-8 编码的,我不知道 Python 为什么不这样做。)
如果用错误的字符集来 encode/decode 会怎样?
1 # 用 ascii 编码含中文的 unicode 字符串 2 u.encode('ascii') # 错误,因为中文无法用 ascii 字符集编码 3 # UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128) 4 5 # 用 gbk 编码含中文的 unicode 字符串 6 u.encode('gbk') # 正确,因为 '关关雎鸠' 可以用中文 gbk 字符集表示 7 # '\xb9\xd8\xb9\xd8\xf6\xc2\xf0\xaf' 8 # 直接 print 上面的 str 会显示乱码,修改环境变量为 zh_CN.GBK 可以看到结果是对的 9 10 # 用 ascii 解码 utf-8 字符串 11 s.decode('ascii') # 错误,中文 utf-8 字符无法用 ascii 解码 12 # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128) 13 14 # 用 gbk 解码 utf-8 字符串 15 s.decode('gbk') # 不出错,但是用 gbk 解码 utf-8 字符流的结果,显然只是乱码 16 # u'\u934f\u51b2\u53e7\u95c6\u5ea8\设定
为什么 Python 这么容易出现字符串编/解码异常?
这要提到处理 Python 编码时容易遇到的两个陷阱。第一个是有关字符串连接的:
1 # -*- coding: utf-8 -*- 2 # file: example2.py 3 4 # 这个是 str 的字符串 5 s = '关关雎鸠' 6 7 # 这个是 unicode 的字符串 8 u = u'关关雎鸠' 9 10 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 有中文,一定会出现错误。
除了字符串连接,% 运算的结果也是一样的:
1 # 正确,所有的字符串都是 str, 不需要 decode 2 "中文:%s" % s # 中文:关关雎鸠 3 4 # 失败,相当于运行:"中文:%s".decode('ascii') % u 5 "中文:%s" % u # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128) 6 7 # 正确,所有字符串都是 unicode, 不需要 decode 8 u"中文:%s" % u # 中文:关关雎鸠 9 10 # 失败,相当于运行:u"中文:%s" % s.decode('ascii') 11 u"中文:%s" % s # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)
其实,sys.getdefaultencoding() 的值是可以用“后门”方式修改的,我不是特别推荐这个解决方案,但是还是贴一下,因为后面有用:
1 # -*- coding: utf-8 -*- 2 # file: example3.py 3 import sys 4 5 # 这个是 str 的字符串 6 s = '关关雎鸠' 7 8 # 这个是 unicode 的字符串 9 u = u'关关雎鸠' 10 11 # 使得 sys.getdefaultencoding() 的值为 'utf-8' 12 reload(sys) # reload 才能调用 setdefaultencoding 方法 13 sys.setdefaultencoding('utf-8') # 设置 'utf-8' 14 15 # 没问题 16 s + u # u'\u5173\u5173\u96ce\u9e20\u5173\u5173\u96ce\u9e20' 17 18 # 同样没问题 19 "中文:%s" % u # u'\u4e2d\u6587\uff1a\u5173\u5173\u96ce\u9e20' 20 21 # 还是没问题 22 u"中文:%s" % s # u'\u4e2d\u6587\uff1a\u5173\u5173\u96ce\u9e20'
可以看到,问题魔术般的解决了。但是注意! sys.setdefaultencoding() 的效果是全局的,如果你的代码由几个不同编码的 Python 文件组成,用这种方法只是按下了葫芦浮起了瓢,让问题变得复杂。
另一个陷阱是有关标准输出的。(另一个陷阱跟本文章关系不大,请参考这一节的原文:http://in355hz.iteye.com/blog/1860787)
以上介绍了基础python编解码知识,下面具体说明ConfigParser如果管理ini配置文件
配置文件编码为UTF-8,内容如下:
1 [section] 2 option=中文字符串
可以通过Notepad++来查看cfg.ini文件的编码方式
ConfigParser可以方便的读取ini配置文件,但是当重新写入时会遇到问题
1 import codecs 2 import ConfigParser 3 4 cfgfile="cfg.ini" 5 6 config = ConfigParser.ConfigParser() 7 config.readfp(codecs.open(cfgfile, "r", "utf-8")) 8 value = config.get("section","option") 9 #config.write(open("cfg2.ini", "w+")) 10 config.write(codecs.open("cfg2.ini", "w+", "utf-8"))
跟踪了一下,在ConfigParser模块的如下位置出现问题:
具体来说是str(value)出错了,因为在例子中的value值为“中文字符串”这已经超出ascii编码的处理范围,我的解决方法是重新实现写入操作,请参考代码:
1 #!/usr/bin/python 2 # -*- coding: utf-8 -*- 3 #------------------------------------------------------------------------------- 4 # Name: 5 # Purpose: 6 # 7 # Author: ZWW 8 # 9 # Created: 19/01/2014 10 # Copyright: (c) ZWW 2014 11 # Licence: <your licence> 12 #------------------------------------------------------------------------------- 13 import codecs 14 import ConfigParser 15 import types 16 import sys 17 18 cfgfile="cfg.ini" 19 20 def ini_set(sec,key,value): 21 try: 22 config.readfp(codecs.open(cfgfile, "r", "utf-8")) 23 if not config.has_section(sec): 24 temp = config.add_section(sec) 25 config.set(sec, key, value) 26 except Exception as e: 27 print("error",str(e)) 28 file = codecs.open(cfgfile, "w", "utf-8") 29 sections=config.sections() 30 for section in sections: 31 #print section 32 file.write("[%s]\n" % section) 33 for (key, value) in config.items(section): 34 if key == "__name__": 35 continue 36 if type(value) in (type(u'') , type('')): 37 file.write(key+"="+value) 38 elif type(value) == type(1): 39 optStr="%s=%d"%(key,value) 40 file.write(optStr) 41 elif type(value) == type(1.5): 42 optStr="%s=%f"%(key,value) 43 file.write(optStr) 44 else: 45 print "do not support this type" 46 print value 47 file.write("\n") 48 file.close() 49 50 if __name__=="__main__": 51 config = ConfigParser.ConfigParser() 52 config.readfp(codecs.open(cfgfile, "r", "utf-8")) 53 value = config.get("section","option") 54 #config.write(open("cfg2.ini", "w+")) 55 #config.write(codecs.open("cfg2.ini", "w+", "utf-8")) 56 print value 57 58 str='中文' 59 print str 60 print type(str) 61 print repr(str) 62 63 utf8str = str.decode('utf-8') 64 print utf8str 65 print type(utf8str) 66 print repr(utf8str) 67 68 ini_set("section","option",utf8str)
执行完程序后输出如下:
1 1 >>> 2 2 中文字符串 3 3 涓枃 4 4 <type 'str'> 5 5 '\xe4\xb8\xad\xe6\x96\x87' 6 6 中文 7 7 <type 'unicode'> 8 8 u'\u4e2d\u6587' 9 9 >>> 10 output of test.py