Python中的嵌套字典,隐式创建不存在的中间容器?

时间:2010-10-18 00:15:51

标签: python dictionary nested implicit creation

我想创建一个可以动态创建的多态结构,只需最少的打字工作量并且非常易读。例如:

a.b = 1
a.c.d = 2
a.c.e = 3
a.f.g.a.b.c.d = cucu
a.aaa = bau

我不想创建一个中间容器,例如:

a.c = subobject()
a.c.d = 2
a.c.e = 3

我的问题与此类似:

What is the best way to implement nested dictionaries?

但我对那里的解决方案不满意,因为我觉得有一个错误:
即使您不想要也会创建项目:假设您要比较2个多态结构:它将在第二个结构中创建存在于第一个结构中的任何属性,并在另一个结构中进行检查。 e.g:

a = {1:2, 3: 4}
b = {5:6}

# now compare them:

if b[1] == a[1]
    # whoops, we just created b[1] = {} !

我也希望得到最简单的符号

a.b.c.d = 1
    # neat
a[b][c][d] = 1
    # yuck

我确实尝试从对象类派生......但是我无法避免像上面那样通过尝试读取属性而产生属性的错误:简单的dir()会尝试创建像“方法”......就像在这个例子中一样,显然已经破解了:

class KeyList(object):
    def __setattr__(self, name, value):
        print "__setattr__ Name:", name, "value:", value
        object.__setattr__(self, name, value)
    def __getattribute__(self, name):
        print "__getattribute__ called for:", name
        return object.__getattribute__(self, name)
    def __getattr__(self, name):
        print "__getattr__ Name:", name
        try:
            ret = object.__getattribute__(self, name)
        except AttributeError:
            print "__getattr__ not found, creating..."
            object.__setattr__(self, name, KeyList())
            ret = object.__getattribute__(self, name)
        return ret

>>> cucu = KeyList()
>>> dir(cucu)
__getattribute__ called for: __dict__
__getattribute__ called for: __members__
__getattr__ Name: __members__
__getattr__ not found, creating...
__getattribute__ called for: __methods__
__getattr__ Name: __methods__
__getattr__ not found, creating...
__getattribute__ called for: __class__

非常感谢!

p.s:到目前为止我找到的最佳解决方案是:

class KeyList(dict):
    def keylset(self, path, value):
        attr = self
        path_elements = path.split('.')
        for i in path_elements[:-1]:
            try:
                attr = attr[i]
            except KeyError:
                attr[i] = KeyList()
                attr = attr[i]
        attr[path_elements[-1]] = value

# test
>>> a = KeyList()
>>> a.keylset("a.b.d.e", "ferfr")
>>> a.keylset("a.b.d", {})
>>> a
{'a': {'b': {'d': {}}}}

# shallow copy
>>> b = copy.copy(a)
>>> b
{'a': {'b': {'d': {}}}}
>>> b.keylset("a.b.d", 3)
>>> b
{'a': {'b': {'d': 3}}}
>>> a
{'a': {'b': {'d': 3}}}

# complete copy
>>> a.keylset("a.b.d", 2)
>>> a
{'a': {'b': {'d': 2}}}
>>> b
{'a': {'b': {'d': 2}}}
>>> b = copy.deepcopy(a)
>>> b.keylset("a.b.d", 4)
>>> b
{'a': {'b': {'d': 4}}}
>>> a
{'a': {'b': {'d': 2}}}

2 个答案:

答案 0 :(得分:1)

我认为至少您需要在__getattr__中检查所请求的attrib不会以__开头和结尾。与该描述匹配的属性实现了已建立的Python API,因此您不应该实例化这些属性。即便如此,您仍然会最终实现一些API属性,例如next。在这种情况下,如果将对象传递给使用duck类型的函数来查看它是否为迭代器,则最终会抛出异常。

创建有效的attrib名称的“白名单”,无论是作为文字集,还是使用简单的公式,都会更好: name.isalpha() and len(name) == 1适用于您在示例中使用的单字母属性。为了更实际的实现,您可能希望定义一组适合您的代码所在域的名称。

我想替代方法是确保您不会动态创建属于某些协议的任何各种属性名称,因为next是迭代协议的一部分。 collections module中的ABC的方法包含部分列表,但我不知道在哪里找到完整的列表。

您还必须跟踪对象是否已创建任何此类子节点,以便您知道如何与其他此类对象进行比较。

如果您希望进行比较以避免自动更新,则必须在检查要比较的对象的__cmp__的类中实施__dict__方法或rich comparison methods。< / p>

我有一种偷偷摸摸的感觉,有一些我没有想到的并发症,这并不奇怪,因为这不是Python应该如何工作。请仔细阅读,并考虑这种方法的复杂程度是否值得你得到它。

答案 1 :(得分:1)

如果你正在寻找的东西不像你原来的帖子那么动态,但更像是你迄今为止最好的解决方案,你可能会看到Ian Bicking的formencode variabledecode是否满足你的需求。软件包本身用于用于Web表单和验证,但是一些方法看起来非常接近您正在寻找的内容。
如果不出意外,它可以作为您自己实施的一个例子。

一个小例子:

>>> from formencode.variabledecode import variable_decode, variable_encode
>>>
>>> d={'a.b.c.d.e': 1}
>>> variable_decode(d)
{'a': {'b': {'c': {'d': {'e': 1}}}}}
>>>
>>> d['a.b.x'] = 3
>>> variable_decode(d)
{'a': {'b': {'c': {'d': {'e': 1}}, 'x': 3}}}
>>>
>>> d2 = variable_decode(d)
>>> variable_encode(d2) == d
True