3

我正在模仿ConfigParser模块的行为来编写一个高度专业化的解析器,该解析器利用我使用的特定应用程序的配置文件中的一些明确定义的结构。配置文件的几个部分包含数百个以Variable_或为前缀的变量和例程映射Routine_,如下所示:

[Map.PRD]
Variable_FOO=LOC1
Variable_BAR=LOC2
Routine_FOO=LOC3
Routine_BAR=LOC4
...

[Map.SHD]
Variable_FOO=LOC1
Variable_BAR=LOC2
Routine_FOO=LOC3
Routine_BAR=LOC4
...

我想保持ConfigParser每个部分存储为单个字典的基本结构,因此用户仍然可以访问经典语法:

config.content['Mappings']['Variable_FOO'] = 'LOC1'

但也可以使用深入到本节的简化 API:

config.vmapping('PRD')['FOO'] = 'LOC1'
config.vmapping('PRD')['BAR'] = 'LOC2'
config.rmapping('PRD')['FOO'] = 'LOC3'
config.rmapping('PRD')['BAR'] = 'LOC4'

目前,我正在通过将部分存储在一个特殊的子类中dict来实现这一点,我已经在该子类中添加了一个prefix属性。解析器的variableand属性将-like 对象的属性设置为or然后修改句柄的属性,将前缀与键粘合在一起以访问适当的项目。它正在工作,但涉及大量样板来实现所有相关的细节,如支持迭代。routineprefixdict'Variable_''Routine_'__getitem____setitem__dict

我想我的理想解决方案是放弃子类dict,并让variableandroutine属性以某种方式呈现dict下面没有前缀的普通对象的“视图”。

更新

这是我实施的解决方案,主要基于@abarnet 的回答:

class MappingDict(object):
    def __init__(self, prefix, d):
        self.prefix, self.d = prefix, d
    def prefixify(self, name):
        return '{}_{}'.format(self.prefix, name)
    def __getitem__(self, name):
        name = self.prefixify(name)
        return self.d.__getitem__(name)
    def __setitem__(self, name, value):
        name = self.prefixify(name)
        return self.d.__setitem__(name, value)
    def __delitem__(self, name):
        name = self.prefixify(name)
        return self.d.__delitem__(name)
    def __iter__(self):
        return (key.partition('_')[-1] for key in self.d
                if key.startswith(self.prefix))
    def __repr__(self):
        return 'MappingDict({})'.format(dict.__repr__(self))

class MyParser(object):
    SECTCRE = re.compile(r'\[(?P<header>[^]]+)\]')
    def __init__(self, filename):
        self.filename = filename
        self.content = {}
        lines = [x.strip() for x in open(filename).read().splitlines() 
                 if x.strip()]
        for line in lines:
            match = re.match(self.SECTCRE, line)
            if match:
                section = match.group('header')
                self.content[section] = {}
            else:
                key, sep, value = line.partition('=')
                self.content[section][key] = value
    def write(self, filename):
        fp = open(filename, 'w')
        for section in sorted(self.content, key=sectionsort):
            fp.write("[%s]\n" % section)
            for key in sorted(self.content[section], key=cpfsort):
                value = str(self.content[section][key])
                fp.write("%s\n" % '='.join([key,value]))
            fp.write("\n")
        fp.close()
    def vmapping(self, nsp):
        section = 'Map.{}'.format(nsp)
        return MappingDict('Variable', self.content[section])
    def rmapping(self, nsp):
        section = 'Map.{}'.format(nsp)
        return MappingDict('Routine', self.content[section])

它是这样使用的:

config = MyParser('myfile.cfg')
vmap = config.vmapping('PRD')
vmap['FOO'] = 'LOC5'
vmap['BAR'] = 'LOC6'
config.write('newfile.cfg')

由此产生newfile.cfg的反映LOC5LOC6变化。

4

2 回答 2

3

我不认为你想在这里继承。您最终会得到两个单独的dict对象,您必须在加载时创建它们,然后在保存时将它们粘贴在一起......</p>

如果这是可以接受的,那么您甚至不需要在正常操作期间为前缀而烦恼;只需在保存时添加前缀,如下所示:

class Config(object):
    def save(self):
        merged = {'variable_{}'.format(key): value for key, value 
                  in self.variable_dict.items()}
        merged.update({'routine_{}'.format(key): value for key, value 
                       in self.routine_dict.items()}
        # now save merged

如果您希望该merged对象始终可见,但不希望经常被调用,请将其设为@property.

如果您想merged定期访问字典,同时访问两个子字典,那么是的,您需要一个视图:

我想我的理想解决方案是放弃子类 dict 并让全局和例程属性以某种方式呈现下面没有前缀的普通 dict 对象的“视图”。

这对于继承将是非常困难的。当然不是继承自dict; 如果您使用的是 Python 3,继承 frombuiltins.dict_items可能会起作用,但它似乎仍然有些牵强。

但是有了委托,这很容易。每个子字典只包含对 parent 的引用dict

class PrefixedDict(object):
    def __init__(self, prefix, d):
        self.prefix, self.d = prefix, d
    def prefixify(self, key):
        return '{}_{}'.format(self.prefix, key)
    def __getitem__(self, key):
        return self.d.__getitem__(self.prefixify(key))
    def __setitem__(self, key, value):
        return self.d.__setitem__(self.prefixify(key), value)
    def __delitem__(self, key):
        return self.d.__delitem__(self.prefixify(key))
    def __iter__(self):
        return (key[len(self.prefix):] for key in self.d 
                if key.startswith(self.prefix)])

你不会以这种dict方式免费获得任何方法——但这是一件好事,因为无论如何它们大多都不正确,对吧?明确委派你想要的人。(如果您确实有一些想要按原样通过,请使用__getattr__它。)

除了在概念上更简单且更难因意外忘记覆盖某些内容而搞砸之外,这还意味着它PrefixDict可以使用任何类型的映射,而不仅仅是dict.


所以,无论你走哪条路,这些对象是在哪里以及如何创建的?

简单的答案是它们是您在构造 a 时创建的属性Config

def __init__(self):
    self.d = {}
    self.variable = PrefixedDict('Variable', self.d)
    self.routine = PrefixedDict('Routine', self.d)

如果这需要是动态的(例如,可以有任意一组前缀),请在加载时创建它们:

def load(self):
    # load up self.d
    prefixes = set(key.split('_')[0] for key in self.d)
    for prefix in prefixes:
        setattr(self, prefix, PrefixedDict(prefix, self.d)

如果您希望能够即时创建它们(所以config.newprefix['foo'] = 3添加'Newprefix_foo'),您可以这样做:

def __getattr__(self, name):
    return PrefixedDict(name.title(), self.d)

但是一旦你使用了动态属性,你真的不得不质疑使用字典(项目)语法是否更简洁,比如config['newprefix']['foo']. 一方面,这实际上会让你调用其中一个子字典'global',就像你原来的问题一样......</p>

或者您可以首先构建字典语法,使用通常称为的attrdict(搜索 ActiveState recipes 和 PyPI 以获取 3000 个实现......),它可以让您自动生成config.newprefixmean config['newprefix'],因此您可以在具有有效标识符时使用属性语法,但回退当您不这样做时,请使用字典语法。

于 2013-01-10T02:22:56.293 回答
0

如何进行有几个选项。

最简单的可能是使用嵌套字典,所以Variable_FOO变成config["variable"]["FOO"]. 您可能希望将 adefaultdict(dict)用于外部字典,以便在向它们添加第一个值时不必担心初始化内部字典。

另一种选择是在单个字典中使用元组键。也就是说,Variable_FOO会变成config[("variable", "FOO")]。使用代码很容易做到这一点,因为您可以简单地分配给config[tuple(some_string.split("_"))]. 不过,我想在这种情况下,您也可以只使用未拆分的字符串作为您的密钥。

最后一种方法允许您使用所需的语法(在哪里Variable_FOO访问为config.Variable["FOO"]),通过在幕后使用__getattr__and a :defaultdict

from collections import defaultdict

class Config(object):
    def __init__(self):
        self._attrdicts = defaultdict(dict)

    def __getattr__(self, name):
        return self._attrdicts[name]

您可以使用 for 的行为来扩展它__setattr____delattr__但可能没有必要。这种方法的唯一严重限制(给定问题的原始版本)是属性名称(如Variable)必须是合法的 Python 标识符。您不能使用带有前导数字的字符串、Python 关键字(如global)或包含空格字符的字符串。

这种方法的一个缺点是它更难以以编程方式使用(例如,通过您的配置文件解析器)。要读取 的值Variable_FOO并将其保存,config.Variable["FOO"]您可能需要使用全局getattr函数,如下所示:

 name, value = line.split("=")
 prefix, suffix = name.split("_")
 getattr(config, prefix)[suffix] = value
于 2013-01-10T02:50:42.823 回答