如何将前缀应用于字典访问?

时间:2013-01-10 01:57:01

标签: python

我正在模仿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属性。解析器的variableroutine属性将prefix类似对象的dict属性设置为'Variable_''Routine_',然后修改{ __getitem__句柄的{1}}和__setitem__个属性将前缀与键一起粘贴以访问相应的项目。它正在工作,但涉及很多样板来实现所有相关的细节,如支持迭代。

我想我的理想解决方案是免除子类dict,并让dictvariable属性以某种方式显示普通routine的“视图”没有前缀的下面的对象。

更新

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

dict

它的使用方式如下:

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.cfgLOC5更改。

2 个答案:

答案 0 :(得分:3)

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

如果这是可以接受的,那么在正常操作期间甚至不需要打扰前缀;只需在保存时执行前缀,如下所示:

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,builtins.dict_items的继承可能会有效,但它似乎仍然是一种延伸。

但是有了代表团,这很容易。每个子词典只包含对父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


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

简单的答案是,它们是您在构建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)

但是一旦你使用动态属性,你真的不得不质疑使用字典(item)语法是否更清晰,比如config['newprefix']['foo']。首先,这实际上可以让你调用其中一个子词典'global',就像你原来的问题一样......

或者你可以先构建字典语法,使用通常所说的attrdict(搜索ActiveState recipes和PyPI for 3000 implementation ...),它可以让你自动config.newprefix表示{{1}所以你可以在有有效的标识符时使用属性语法,但是当你没有时可以使用属性语法。

答案 1 :(得分:0)

如何进行有几种选择。

最简单的可能是使用嵌套字典,因此Variable_FOO变为config["variable"]["FOO"]。您可能希望使用defaultdict(dict)作为外部字典,因此在向其添加第一个值时无需担心初始化内部字典。

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

最终方法允许您使用所需的语法(Variable_FOO作为config.Variable["FOO"]访问的位置),在幕后使用__getattr__defaultdict:< / p>

from collections import defaultdict

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

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

您可以使用__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