13.3 使用INI文件保存配置

    INI文件格式源于早期版本的Windows。用于解析这个文件的模块为configparser

    有关更多INI文件的详细内容,可以参见Wikipedia上面这篇文章:

    http://en.wikipedia. org/wiki/INI_file。

    在INI文件中,每个部分包括了节和属性。在我们的主程序中,包括了3个部分:表配置、玩家配置和全局模拟器数据的收集。

    一个INI文件看起来如以下代码所示。

    ; Default casino rules
    [table]
       dealer= Hit17
       split= NoResplitAces
       decks= 6
       limit= 50
       payout= (3,2)

    ; Player with SomeStrategy
    ; Need to compare with OtherStrategy
    [player]
       play= SomeStrategy
       betting= Flat
       rounds= 100
       stake= 50

    [simulator]
       samples= 100
       outputfile= p2_c13_simulation.dat

    我们将参数分成了3个部分。在每个部分中,我们提供了一些命名的参数,它们与类名和之前所示的应用初始化模型中的初始化值相对应。

    可以很简单地对单一文件进行解析。

    import configparser
    config = configparser.ConfigParser()
    config.read('blackjack.ini')

    我们创建了一个解析器实例,然后将目标配置文件名传入解析器。解析器会读取文件,查找其中的每一部分,并对每个部分中所包含的各自的特性进行查找。

    如果需要文件支持多个位置,可以使用config.read(config_names)。当为Config Parser.read()提供一个文件名列表时,它将按顺序读取每个文件。往往期望先读取最具一般性的,最后读取特殊化的文件。通用的配置文件为软件安装的一部分,将提供用于解析的默认值。而用户特殊指定的配置会在之后被解析,覆盖其中一些默认值。

    一旦完成了文件解析,需要能够使用不同的参数和设置。以下所示的这个函数,基于指定的配置对象完成对象的创建,配置对象来自对配置文件的解析。我们将这个过程分为3个部分,以下是创建Table实例的部分。

    def main_ini( config ):
       dealer_nm= config.get('table','dealer', fallback='Hit17')
       dealer_rule= {'Hit17': Hit17(),
         'Stand17': Stand17()}.get(dealer_nm, Hit17())
       split_nm= config.get('table','split', fallback='ReSplit')
       split_rule= {'ReSplit': ReSplit(),
         'NoReSplit': NoReSplit(),
         'NoReSplitAces': NoReSplitAces()}.get(split_nm, ReSplit())
       decks= config.getint('table','decks', fallback=6)
       limit= config.getint('table','limit', fallback=100)
       payout= eval( config.get('table','payout', fallback='(3,2)') )
       table= Table( decks=decks, limit=limit, dealer=dealer_rule,
         split=split_rule, payout=payout )

    在INI文件中的[table]部分,使用其中的属性来选择类名,并提供初始化值。主要存在以下3种情况。

    • 从字符串映射到类名:基于字符串类名使用映射来实现对象的查找。上例用来创建dealer_rule和split_rule,为了便于修改,可以将这个映射重构到单独的工厂方法中。
    • 获取一个值,可使用ConfigParser来解析:这个类可以直接处理str、int、float和bool。这个类中包括了一个复杂的映射,从字符串到布尔类型,使用了大量的公共代码以及True和False的同义词。
    • 非内置类型的处理:对于payout的情况,我们使用了一个字符串值,'(3,2)',Config.parser中并没有直接支持这个数值类型。有两种方法可以解决这个问题。可以自己试着解析,或者把它的值当作合法的 Python 表达式然后使用 Python来完成。这样的话,就需要使用eval()函数。一些程序员会认为这样做存在安全问题。下一节会对此进行说明。

    以下是这个例子的第2部分,使用了INI文件中的[player]部分中的属性来选择类和参数值。

    player_nm= config.get('player','play', fallback='SomeStrategy')
    player_rule= {'SomeStrategy': SomeStrategy(),
      'AnotherStrategy': AnotherStrategy()}.get(player_nm,SomeStrategy())
    bet_nm= config.get('player','betting', fallback='Flat')
    betting_rule= {'Flat': Flat(),
      'Martingale': Martingale(),
      'OneThreeTwoSix': OneThreeTwoSix()}.get(bet_nm,Flat())
    rounds= config.getint('player','rounds', fallback=100)
    stake= config.getint('player','stake', fallback=50)
    player= Player( play=player_rule, betting=betting_rule,
      rounds=rounds, stake=stake )

    它使用了从字符串到类的映射和内置的数值类型。它初始化了两个策略对象,然后基于这两种策略和两个整型在配置文件中的值来创建Player实例。

    以下是最后一部分,在这里创建了全局的模拟器。

    outputfile= config.get('simulator', 'outputfile',fallback='blackjack.csv')
    samples= config.getint('simulator', 'samples', fallback=100)
    simulator= Simulate( table, player, samples )
    with open(outputfile,"w",newline="") as results:
      wtr= csv.writer( results )
      for gamestats in simulator:
        wtr.writerow( gamestats )

    使用了两个在[simulation]部分中的参数,它们在对象创建范围的外部。outputfile特性用于命名一个文件,samples特性将作为参数提供给方法。