但行好事,莫问前程

挖了太多坑,一点点填回来

Python读取ini文件

hack, ini, php, python

标准的ini文件中,应该有一个类似[section]的部分作为正文的开头,但是很多情况下,ini文件都是不标准的,比如下面这个格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
master.host                 = stats10.mezimedia.com
master.username             = scltrk
master.password             = Mhok8homL7
master.dbname               = tracking

slave.host                  = stats11.mezimedia.com
slave.port                  = 3308
slave.username              = scltrk
slave.password              = Mhok8homL7
slave.dbname                = tracking

msgsite                     = smarter
offset                      = 20000

在PHP中,对ini文件的格式容错率较高,不管是不是以[section]开头,PHP总能以正确的方式解析,不管是用原生的parse_ini_file函数还是Zend_Config_Ini类库,都可以非常方便无错地解析ini。

下面是用parse_ini_file解析文件的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();

$iniFile = './smus.ini';

Zend_Debug::dump(parse_ini_file($iniFile));
/* 
输出结果
array(12) {
  ["master.host"] => string(21) "stats10.mezimedia.com"
  ["master.username"] => string(6) "scltrk"
  ["master.password"] => string(10) "Mhok8homL7"
  ["master.dbname"] => string(8) "tracking"
  ["slave.host"] => string(21) "stats11.mezimedia.com"
  ["slave.port"] => string(4) "3308"
  ["slave.username"] => string(6) "scltrk"
  ["slave.password"] => string(10) "Mhok8homL7"
  ["slave.dbname"] => string(8) "tracking"
  ["msgsite"] => string(7) "smarter"
  ["offset"] => string(5) "20000"
}
 */

Zend_Debug::dump(parse_ini_file($iniFile, true));
/*
输出结果和上面的语句一样,但是如果ini文件中有[section]的话,输出数组的根key就是["section"]
 */

而如果用Zend_Config_Ini来解析的话,输出的结构会更清晰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$config = new Zend_Config_Ini($iniFile);
Zend_Debug::dump($config->toArray());
/*
输出结果如下。同样如果ini文件中有[section]的话,输出数组的根key就是["section"]
array(5) {
  ["master"] => array(4) {
    ["host"] => string(21) "stats10.mezimedia.com"
    ["username"] => string(6) "scltrk"
    ["password"] => string(10) "Mhok8homL7"
    ["dbname"] => string(8) "tracking"
  }
  ["slave"] => array(5) {
    ["host"] => string(21) "stats11.mezimedia.com"
    ["port"] => string(4) "3308"
    ["username"] => string(6) "scltrk"
    ["password"] => string(10) "Mhok8homL7"
    ["dbname"] => string(8) "tracking"
  }
  ["msgsite"] => string(7) "smarter"
  ["offset"] => string(5) "20000"
}
 */

但是在Python中用来解析ini文件的ConfigParser类,则对ini文件的格式有着非常严格的要求——必须在文件开头含有[section]部分,否则在读取时会抛出ConfigParser.MissingSectionHeaderError。为了能让Python解析ini也可以容错,有必要做一下hack。

(demo.py) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/usr/bin/python
#coding: utf-8

import ConfigParser

class FakeSectionHead(object):
    """
    文件wrapper
    """
    def __init__(self, fp):
        """如果没有section的话,默认添加一个叫[default]的section"""
        self.fp = fp
        self.section = "[default]\n"

    def readline(self):
        """第一次输出[default], 以后就输出文件的内容"""
        if self.section:
            try:
                return self.section
            finally:
                self.section = None
        else:
            return self.fp.readline()

ini = './config/smus.ini'

def parse_ini_file(ini):
    p = ConfigParser.ConfigParser()
    try:
        """标准格式的ini文件,直接读取内容"""
        p.readfp(open(ini, 'rb'))
    except ConfigParser.MissingSectionHeaderError, error:
        """非标准格式的ini文件,用FakeSectionHead类包装一下"""
        print error
        """
        此处输出:
        File contains no section headers.
        file: ./config/smus.ini, line: 1
        'master.host                 = stats10.mezimedia.com\r\n'
        """
        p.readfp(FakeSectionHead(open(ini, 'rb')))

    return p

p = parse_ini_file(ini)
print p.items('default')
"""
[('master.host', 'stats10.mezimedia.com'), 
 ('master.username', 'scltrk'), 
 ('master.password', 'Mhok8homL7'), 
 ('master.dbname', 'tracking'), 
 ('slave.host', 'stats11.mezimedia.com'), 
 ('slave.port', '3308'), 
 ('slave.username', 'scltrk'), 
 ('slave.password', 'Mhok8homL7'), 
 ('slave.dbname', 'tracking'), 
 ('msgsite', 'smarter'), 
 ('offset', '20000')]
"""
print p.sections()
"""
['default']
"""

如此一来就能正常工作了。部分内容参考自StackOverflow——parsing .properties file in Python

Have a nice day!