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特性将作为参数提供给方法。
