有效地访问任意深度的词典

Ric*_*nix 29 python recursion dictionary python-2.7

假设我有一个像这样的多级字典

mydict = {
    'first': {
        'second': {
            'third': {
                'fourth': 'the end'
             }
         }
     }
}
Run Code Online (Sandbox Code Playgroud)

我想像这样访问它

test = get_entry(mydict, 'first.second.third.fourth')
Run Code Online (Sandbox Code Playgroud)

到目前为止我所拥有的是什么

def get_entry(dict, keyspec):
    keys = keyspec.split('.')

    result = dict[keys[0]]
    for key in keys[1:]:
       result = dict[key]

    return result
Run Code Online (Sandbox Code Playgroud)

有更有效的方法吗?根据%timeit,函数的运行时间是1.26us,而访问字典的标准方式是这样的

foo = mydict['first']['second']['third']['fourth']
Run Code Online (Sandbox Code Playgroud)

需要541ns.如果可能的话,我正在寻找将其修剪到800ns范围的方法.

谢谢

cs9*_*s95 12

真的只有一个解决方案.重建你的字典.但只做一次.

def recursive_flatten(mydict):
    d = {}
    for k, v in mydict.items():
        if isinstance(v, dict):
            for k2, v2 in recursive_flatten(v).items():
                d[k + '.' + k2] = v2 
        else:
            d[k] = v
    return d
Run Code Online (Sandbox Code Playgroud)

In [786]: new_dict = recursive_flatten(mydict); new_dict
Out[786]: {'first.second.third.fourth': 'the end'}
Run Code Online (Sandbox Code Playgroud)

(还有一些测试)

In [788]: recursive_flatten({'x' : {'y' : 1, 'z' : 2}, 'y' : {'a' : 5}, 'z' : 2})
Out[788]: {'x.y': 1, 'x.z': 2, 'y.a': 5, 'z': 2}

In [789]: recursive_flatten({'x' : 1, 'y' : {'x' : 234}})
Out[789]: {'x': 1, 'y.x': 234}
Run Code Online (Sandbox Code Playgroud)

从此处开始,每次访问都是恒定的时间.

现在,只需使用即可访问您的价值new_dict['first.second.third.fourth'].应该适用于任何包含自引用的任意嵌套字典.

请注意,每个解决方案都有其公平的权衡,这也不例外.除非你在数据上发出数百万条查询,否则预处理是一种可接受的开销,那就是这样.使用其他解决方案,您只是回避问题而不是解决它 - 这是处理字典的结构.OTOH,如果你要做到这一点,一旦许多这样类似的数据结构,它是没有意义的预处理只是一个单一的查询,在这种情况下,你可能更喜欢另一种解决方案.

  • 请注意,这似乎只允许访问最终的嵌套级别,例如,您不能访问`new_dict ['first.second']` (2认同)
  • 虽然在空间方面,你的(在评论中扩展)解决方案不能很好地扩展(线性读取). (2认同)

tde*_*ney 11

通过使用缓存来分割字符串,我将代码收紧了一段时间,但性能提升了20%,但是性能提升了20%.如果您多次使用相同的规格,那只会产生影响.以下是示例实现和要测试的配置文件脚本.

test.py

mydict = {
    'first': {
        'second': {
            'third': {
                'fourth': 'the end'
             }
         }
     }
}

# original
def get_entry(dict, keyspec):
    keys = keyspec.split('.')

    result = dict[keys[0]]
    for key in keys[1:]:
       result = result[key]

    return result

# tighten up code
def get_entry_2(mydict, keyspec):
    for key in keyspec.split('.'):
        mydict = mydict[key]
    return mydict

# use a cache
cache = {}
def get_entry_3(mydict, keyspec):
    global cache
    try:
        spec = cache[keyspec]
    except KeyError:
        spec = tuple(keyspec.split('.'))
        cache[keyspec] = spec

    for key in spec:
        mydict = mydict[key]
    return mydict

if __name__ == "__main__":
    test = get_entry(mydict, 'first.second.third.fourth')
    print(test)
Run Code Online (Sandbox Code Playgroud)

profile.py

from timeit import timeit
print("original get_entry")
print(timeit("get_entry(mydict, 'first.second.third.fourth')",
    setup="from test import get_entry, mydict"))

print("get_entry_2 with tighter code")
print(timeit("get_entry_2(mydict, 'first.second.third.fourth')",
    setup="from test import get_entry_2, mydict"))

print("get_entry_3 with cache of split spec")
print(timeit("get_entry_3(mydict, 'first.second.third.fourth')",
    setup="from test import get_entry_3, mydict"))

print("just splitting a spec")
print(timeit("x.split('.')", setup="x='first.second.third.fourth'"))
Run Code Online (Sandbox Code Playgroud)

我机器上的时间是

original get_entry
4.148535753000033
get_entry_2 with tighter code
3.2986323120003362
get_entry_3 with cache of split spec
1.3073233439990872
just splitting a spec
1.0949148639992927
Run Code Online (Sandbox Code Playgroud)

请注意,拆分规范对于此功能来说是相对昂贵的操作.这就是缓存有帮助的原因.

  • 看起来你是唯一关注性能的人. (3认同)

use*_*203 10

我更新了如何使用点"答案"的答案.访问字典成员?使用初始转换,然后将用于嵌套字典:

您可以使用以下类来允许字典的点索引:

class dotdict(dict):
    """dot.notation access to dictionary attributes"""
    __getattr__ = dict.get
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__
Run Code Online (Sandbox Code Playgroud)

但是,如果所有嵌套字典也是类型,则仅支持嵌套dotdict.这就是以下辅助函数的用武之地:

def dct_to_dotdct(d):
    if isinstance(d, dict):
        d = dotdict({k: dct_to_dotdct(v) for k, v in d.items()})
    return d
Run Code Online (Sandbox Code Playgroud)

此函数必须在嵌套字典上运行一次,然后可以使用点索引对结果进行索引.

这里有些例子:

In [13]: mydict
Out[13]: {'first': {'second': {'third': {'fourth': 'the end'}}}}

In [14]: mydict = dct_to_dotdct(mydict)

In [15]: mydict.first.second
Out[15]: {'third': {'fourth': 'the end'}}

In [16]: mydict.first.second.third.fourth
Out[16]: 'the end'
Run Code Online (Sandbox Code Playgroud)

关于性能的说明:与标准字典访问相比,这个答案很慢,我只是想提供一个实际上对字典使用"点访问"的选项.


kab*_*nus 7

这是一个类似于chrisz的解决方案,但你不需要事先做任何事情.:

class dictDotter(dict):
    def __getattr__(self,key):
        val = self[key]
        return val if type(val) != dict else dictDotter(val)
Run Code Online (Sandbox Code Playgroud)

,只是x=dictDotter(originalDict)会让你有任意点得到(`x.first.second ...).我会注意到它的速度是chrisz解决方案的两倍,而且速度是你的速度的9倍(在我的机器上,大约是).

所以,如果你坚持做这项工作,@ tdelaney似乎提供了唯一真正的性能提升.

另一种选择比你拥有的更好(就运行时而言):

class dictObjecter:
    def __init__(self,adict):
        for k,v in adict.items():
            self.__dict__[k] = v
            if type(v) == dict: self.__dict__[k] = dictObjecter(v)
Run Code Online (Sandbox Code Playgroud)

这将使你的dict中出现一个物体,所以点符号通常是正常的.这样可以将运行时间提高到你所拥有的3倍,所以不坏,但代价是要超越你的dict,并用其他东西替换它.

这是总测试代码:

from timeit import timeit

class dictObjecter:
    def __init__(self,adict):
        for k,v in adict.items():
            self.__dict__[k] = v
            if type(v) == dict: self.__dict__[k] = dictObjecter(v)

class dictDotter(dict):
    def __getattr__(self,key):
        val = self[key]
        return val if type(val) != dict else dictDotter(val)

def get_entry(dict, keyspec):
    keys = keyspec.split('.')

    result = dict[keys[0]]
    for key in keys[1:]:
        result = result[key]

    return result

class dotdict(dict):
    """dot.notation access to dictionary attributes"""
    __getattr__ = dict.get
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

def dct_to_dotdct(d):
    if isinstance(d, dict):
        d = dotdict({k: dct_to_dotdct(v) for k, v in d.items()})
    return d

x = {'a':{'b':{'c':{'d':1}}}}
y = dictDotter(x)
z = dct_to_dotdct(x)
w = dictObjecter(x)
print('{:15} : {}'.format('dict dotter',timeit('y.a.b.c.d',globals=locals(),number=1000)))
print('{:15} : {}'.format('dot dict',timeit('z.a.b.c.d',globals=locals(),number=1000)))
print('{:15} : {}'.format('dict objecter',timeit('w.a.b.c.d',globals=locals(),number=1000)))
print('{:15} : {}'.format('original',timeit("get_entry(x,'a.b.c.d')",globals=locals(),number=1000)))
print('{:15} : {:.20f}'.format('best ref',timeit("x['a']['b']['c']['d']",globals=locals(),number=1000)))
Run Code Online (Sandbox Code Playgroud)

我提供了最后一次常规查找作为最佳参考.Windows Ubuntu子系统上的结果:

dict dotter     : 0.0035500000003594323
dot dict        : 0.0017939999997906853
dict objecter   : 0.00021699999979318818
original        : 0.0006629999998040148
best ref        : 0.00007999999979801942
Run Code Online (Sandbox Code Playgroud)

因此,客观化的字典是常规字典查找的3倍 - 所以如果速度很重要,为什么要这样呢?