python2.7 内置ConfigParser支持Unicode读写

1 python编码基础

对应 C/C++ 的 char 和 wchar_t, Python 也有两种字符串类型,str 与 unicode:

python2.7 内置ConfigParser支持Unicode读写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 方法转换:

python2.7 内置ConfigParser支持Unicode读写
1 # 从 str 转换成 unicode
2 print s.decode(utf-8)   # 关关雎鸠
3 
4 # 从 unicode 转换成 str
5 print u.encode(utf-8)   # 关关雎鸠
encode/decode转换

为什么从 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 会怎样?

python2.7 内置ConfigParser支持Unicode读写设置编解码字符集
 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 编码时容易遇到的两个陷阱。第一个是有关字符串连接的: 

python2.7 内置ConfigParser支持Unicode读写
 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 有中文,一定会出现错误。

除了字符串连接,% 运算的结果也是一样的:

python2.7 内置ConfigParser支持Unicode读写
 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() 的值是可以用“后门”方式修改的,我不是特别推荐这个解决方案,但是还是贴一下,因为后面有用:

python2.7 内置ConfigParser支持Unicode读写
 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‘
setdefaultencoding

可以看到,问题魔术般的解决了。但是注意! sys.setdefaultencoding() 的效果是全局的,如果你的代码由几个不同编码的 Python 文件组成,用这种方法只是按下了葫芦浮起了瓢,让问题变得复杂。
另一个陷阱是有关标准输出的。(另一个陷阱跟本文章关系不大,请参考这一节的原文:http://in355hz.iteye.com/blog/1860787

2 python读写ini配置文件

以上介绍了基础python编解码知识,下面具体说明ConfigParser如果管理ini配置文件
配置文件编码为UTF-8,内容如下:

python2.7 内置ConfigParser支持Unicode读写
1 [section]
2 option=中文字符串
cfg.ini

可以通过Notepad++来查看cfg.ini文件的编码方式
python2.7 内置ConfigParser支持Unicode读写
ConfigParser可以方便的读取ini配置文件,但是当重新写入时会遇到问题

python2.7 内置ConfigParser支持Unicode读写
 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"))
write error

跟踪了一下,在ConfigParser模块的如下位置出现问题:
python2.7 内置ConfigParser支持Unicode读写
具体来说是str(value)出错了,因为在例子中的value值为“中文字符串”这已经超出ascii编码的处理范围,我的解决方法是重新实现写入操作,请参考代码:

python2.7 内置ConfigParser支持Unicode读写
 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)
test.py

执行完程序后输出如下:

python2.7 内置ConfigParser支持Unicode读写
 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
Output of test.py

新的cfg.ini内容如下:
python2.7 内置ConfigParser支持Unicode读写

python2.7 内置ConfigParser支持Unicode读写

上一篇:vue-quill-editor富文本编译器使用


下一篇:再读C++线程池