13.8 使用JSON或YAML文件存储配置
可以使用JSON或YAML文件完成配置值的存储,这种方式相对容易一些。语法相对友好一些。可以使用YAML表达各种各样的数据。然而,使用JSON的话,对象类的范围较窄一些,可像以下代码这样来定义一个JSON配置文件。
{
"table":{
"dealer":"Hit17",
"split":"NoResplitAces",
"decks":6,
"limit":50,
"payout":[3,2]
},
"player":{
"play":"SomeStrategy",
"betting":"Flat",
"rounds":100,
"stake":50
},
"simulator":{
"samples":100,
"outputfile":"p2_c13_simulation.dat"
}
}
JSON文档看起来像是嵌套的字典,这正是在加载文件时所创建的对象,可以使用以下代码来加载一个配置文件。
import json
config= json.load( "config.json" )
这样就可以使用config['table']['dealer']来查找用于庄家规则的类,也可以使用['player']['betting']来查找玩家特定的玩牌策略的类名。
不像 INI文件,可以像对待一个值序列那样,对tuple进行编码。按照这个逻辑,config['table']['payout']的值可以当作是两个元素的序列,并不必严格遵照tuple那样的处理方式。然而,在访问配置文件时没有必要一定要使用ast.literal_eval()。
以下代码演示了我们如何使用这个嵌套结构,这里只列出main_nested_dict()函数的第1部分。
def main_nested_dict( config ):
dealer_nm= config.get('table',{}).get('dealer', 'Hit17')
dealer_rule= {'Hit17':Hit17(),
'Stand17':Stand17()}.get(dealer_nm, Hit17())
split_nm= config.get('table',{}).get('split', 'ReSplit')
split_rule= {'ReSplit':ReSplit(),
'NoReSplit':NoReSplit(),
'NoReSplitAces':NoReSplitAces()}.get(split_nm, ReSplit())
decks= config.get('table',{}).get('decks', 6)
limit= config.get('table',{}).get('limit', 100)
payout= config.get('table',{}).get('payout', (3,2))
table= Table( decks=decks, limit=limit, dealer=dealer_rule,
split=split_rule, payout=payout )
这与之前所演示的main_ini()函数类似。如果使用configparser与上一个版本的实现对比一下,就会发现复杂度基本是一样的。命名方面相对简单了一些,使用了get('table',{}).get('decks')来代替config.getint('table','decks')。
最大的不同是加粗显式的那行代码。JSON格式提供了正确解码的整数值和值序列。不必使用eval()或ast.literal_eval()来对tuple进行解码。另一部分中,创建Player和Simulate对象的配置,和main_ini()的版本是类似的。
13.8.1 使用压平的JSON配置
如果使用将多个配置文件进行集成的方式来完成默认值的初始化,就不能同时使用ChainMap和嵌套的字典结构。因此,要么对程序的参数压平,要么对不同的参数数据源进行合并。
可以在名字之间简单地使用.操作符来对命名进行压平操作。下面所示的是一个压平后的JSON文件。
{
"player.betting": "Flat",
"player.play": "SomeStrategy",
"player.rounds": 100,
"player.stake": 50,
"table.dealer": "Hit17",
"table.decks": 6,
"table.limit": 50,
"table.payout": [3, 2],
"table.split": "NoResplitAces",
"simulator.outputfile": "p2_c13_simulation.dat",
"simulator.samples": 100
}
以上文件的优势是,可以使用ChainMap计算来自不同参数源的配置值。也简化了对指定参数值查找的语法。当拿到一个配置文件名列表时,config_names,可以像如下代码这样操作。
config = ChainMap( *[json.load(file) for file in reversed(config_names)] )
以上代码基于一个反序的配置文件名列表创建了ChainMap。为什么要反序?因为所希望的列表顺序为,特殊化的在前面,一般化的在后面。configparser所需要的列表也是反序的,而且通过将新项持续地添加到列表头部以达到持续创建ChainMap的目的,反序也是必需的。在这里,只是简单地加载一个dict列表进入ChainMap中,而且在使用key进行搜索时,第1个dict将出现在搜索结果的第1个位置。
可以使用一个类似这样的方法来加强ChainMap的实现。以下代码演示了第1部分,创建Table实例。
def main_cm( config ):
dealer_nm= config.get('table.dealer', 'Hit17')
dealer_rule= {'Hit17':Hit17(),
'Stand17':Stand17()}.get(dealer_nm, Hit17())
split_nm= config.get('table.split', 'ReSplit')
split_rule= {'ReSplit':ReSplit(),
'NoReSplit':NoReSplit(),
'NoReSplitAces':NoReSplitAces()}.get(split_nm, ReSplit())
decks= int(config.get('table.decks', 6))
limit= int(config.get('table.limit', 100))
payout= config.get('table.payout', (3,2))
table= Table( decks=decks, limit=limit, dealer=dealer_rule,
split=split_rule, payout=payout )
至于其余部分,创建Player和Simulate对象的配置,和main_ini()版本的实现是类似的。
使用configparser将这个版本的实现与之前的比较,可以看到复杂度基本是相同的。以上实现在命名上相对简单,使用了int(config.get('table.decks'))来代替config.getint('table','decks')。
13.8.2 加载YAML配置
因为YAML语法包括了JSON的语法,因此对于之前的示例,可以使用YAML或者JSON加载。以下定义的是一个JSON文件,使用的是嵌套字典的写法。
player:
betting: Flat
play: SomeStrategy
rounds: 100
stake: 50
table:
dealer: Hit17
decks: 6
limit: 50
payout: [3, 2]
split: NoResplitAces
simulator: {outputfile: p2_c13_simulation.dat, samples: 100}
比起纯JSON,这个文件的语法好一些,更容易进行编辑。对于配置中大多数为字符串和整数的应用,这种格式有很多好处。加载过程和加载JSON文件是相同的。
import yaml
config= yaml.load( "config.yaml" )
和嵌套字典所遇到的限制一样,需要对命名进行压平来解决默认值的问题。
如果要支持除字符串和整数外的更多类型,可以通过对类名进行编码并创建自定义的实例来扩展YAML。这里是一个YAML文件,用于创建模拟器所需的配置对象。
# Complete Simulation Settings
table: !!python/object:main.Table
dealer: !!python/object:main.Hit17 {}
decks: 6
limit: 50
payout: !!python/tuple [3, 2]
split: !!python/object:main.NoReSplitAces {}
player: !!python/object:main.Player
betting: !!python/object:main.Flat {}
initstake: 50
maxrounds: 100
play: !!python/object:__main.SomeStrategy {}
rounds: 0
stake: 63.0
samples: 100
outputfile: p2_c13_simulation9.dat
我们对类名和实例的构造器使用 YAML 进行了编码,这样就可以对 Table 和 Player定义完整的初始化,可以像以下这样使用初始化文件。
import yaml
if name == "main":
config= yaml.load( yaml1_file )
simulate( config['table'], config['player'],
config['outputfile'], config['samples'] )
在上例中,可以看到YAML配置文件可以直接被编辑。YAML格式具备Python同样的能力,但需要更复杂的语法。对于上例来说,使用Python配置脚本比YAML更好一些。
