13.7 为默认值和重写使用链映射
我们通常会有一个配置文件层次结构。之前,我们列出了一些配置文件可以被安装的位置。例如,configparser模块被定义用于按顺序读取一些文件,然后通过使用后面文件的值对前面文件的值进行覆盖实现配置的集成。
我们可以使用collection.ChainMap类实现元素默认值处理。有关这个类的一些背景,可参见第6章“创建容器和集合”。我们需要将配置参数保存为dict实例,可使用exec()直接执行Python语言的配置文件。
使用这种方式,需要将配置参数设计为压平的字典值。包含了大量的复杂配置值,它们来自多个资源的集成,这可能会给应用程序带来负担。有关压平的命名,接下来会介绍一种不错的方式。
首先,基于标准位置创建一个文件列表。
from collections import ChainMap
import os
configname= "config.py"
configlocations = (
os.path.expanduser("~thisapp/"), # or thisapp.__file,
"/etc",
os.path.expanduser("~/"),
os.path.curdir,
)
candidates = ( os.path.join(dir,config_name)
for dir in config_locations )
config_names = ( name for name in candidates if os.path.exists(name) )
我们以一个目录列表为开始:安装目录。它包括一个系统的全局目录、一个用户的home目录和当前的工作目录。我们将配置文件名放入每个目录中,然后确认文件实际是存在的。
一旦有了候选文件的名称,可以通过对每个文件折叠来创建ChainMap。
config = ChainMap()
for name in config_names:
config= config.new_child()
exec(name, globals(), config)
simulate( config.table, config.player, config.outputfile, config.
samples)
每个文件包含了新建一个空的可以使用本地变量来更新的 map,exec()函数会将文件的本地变量添加到由new_child()创建的空的map中。每个新的child更特殊化一些,会覆盖之前加载的配置。
在ChainMap中,每个名称的解析都是通过对整个map的各个值进行搜索得到的。当加载了两个配置文件到ChainMap时,就得到了如下代码所示的结构。
ChainMap(
{'player': <main.Player object at 0x10101a710>, 'outputfile':
'p2c13simulation7a.dat', 'playerrule': <main.AnotherStrategy
object at 0x10101aa90>},
{'dealer_rule': <__main.Hit17 object at 0x10102a9d0>, 'betting
rule': <main.Flat object at 0x10101a090>, 'splitrule': <main.
NoReSplitAces object at 0x10102a910>, 'samples': 100, 'playerrule':
<main.SomeStrategy object at 0x10102a8d0>, 'table': <main.
Table object at 0x10102a890>, 'outputfile': 'p2_c13_simulation7.dat',
'player': <__main.Player object at 0x10101a210>},
{})
我们有一个maps序列。第1个map中是最后定义的本地变量,它们是重写的值;第2个map中有应用的默认值;还有第3张空的map,因为ChainMap总是至少包括一张空map。当创建config的初始值时,一张空的map就被创建了。
这种做法的唯一缺陷是初始化必须使用字典格式,config['table'],config ['player']。可以对ChainMap()进行扩展,为字典项添加属性访问。
以下是一个ChainMap的子类。如果认为getitem()的字典格式太笨重就可以使用这个类。
class AttrChainMap( ChainMap ):
def getattr( self, name ):
if name == "maps":
return self.dict['maps']
return super().get(name,None)
def setattr( self, name, value ):
if name == "maps":
self.dict['maps']= value
return
self[name]= value
现在就可以使用config.table来代替config['table']了。在ChainMap的扩展实现中存在一个重要的限制:我们不能够使用maps 作为一个属性。Maps 键是父类ChainMap中的第1级属性。
