从一组嵌套字典中创建键值对列表的大多数pythonic和最快的方法?

时间:2015-02-04 02:47:36

标签: python

我已经提出了以下解决方案,但它非常难看(参见原始解决方案)。我对修改后的解决方案非常满意。任何人都有更清洁/更快的方法来完成相同的输出?

其他要求:

  • 必须接受任何值并返回键值对列表。
  • 最后一个键必须跟踪使用点语法访问值的键列表。
  • 必须返回键值对或字典列表。
  • 当没有提供base_key时,
  • 必须删除前导.

我的修订解决方案:

def create_nested_kvl(v, base_key=None):
    kvl = []
    if not isinstance(v, dict):
        kvl.append((base_key,v))
    else:
        def iterate(v, k):
            for ki, vi in v.items():
                ki = '%s.%s' % (k, ki) if k else ki
                iterate(vi, ki) if isinstance(vi, dict) else kvl.append((ki, vi))
        iterate(v, base_key)
    return kvl

我的原始解决方案:

def create_nested_kvl(v, base_key=''):
    """ Creates a list of dot syntax key value pairs from a nested dictionary.
    :param      v: The value suspected to be a nested dictionary.
    :param      k: Base key
    :return:    [(k,v)]
    :rtype:     list
    """
    if not isinstance(v, dict):
        return [(base_key,v)]

    kvl = []
    def iterate(v, k):
        for kd, vd in v.items():
            v = vd
            kd = '%s.%s' % (k, kd) if k else kd
            kvl.append((kd, v))

    iterate(v, base_key)
    for k, v in kvl:
        if isinstance(v, dict):
            iterate(v, k)
            kvl.remove((k,v))
    return kvl

输入:

v = {'type1':'type1_val',
     'type2':'type2_val',
     'object': {
          'k1': 'val1',
          'k2': 'val2',
          'k3': {'k31': {
                     'k311': 'val311',
                     'k322': 'val322',
                     'k333': 'val333'
                     },
                'k32': 'val32',
                'k33': 'val33'}}}

create_nested_kvl(v, 'base')

输出:

[('base.type1', 'type1_val'),
 ('base.type2', 'type2_val'),
 ('base.object.k2', 'val2'),
 ('base.object.k1', 'val1'),
 ('base.object.k3.k33', 'val33'),
 ('base.object.k3.k32', 'val32'),
 ('base.object.k3.k31.k311', 'val311'),
 ('base.object.k3.k31.k333', 'val333'),
 ('base.object.k3.k31.k322', 'val322')]

注意:

  • Alex Martelli提出的发电机解决方案非常灵活。不幸的是,它似乎比我的第一个和修订后的解决方案慢一点。此外,它返回一个仍然需要转换为列表或poof的生成器,它已经消失了。

timeit结果@ number = 1000000:

generator : 0.911420848311 (see alex's answer)
original  : 0.720069713321
revised   : 0.660259814902

best      : 0.660259814902 
* as Alex pointed out, my late night rounding skills are horrific.
It's 27% faster not twice as fast (my bad).

1 个答案:

答案 0 :(得分:3)

除了dicts中的键的排序是任意的,并且如果空键需要修改前导.可能需要修改(规范不清楚):

def create_nested_kvl(v, k=''):
    if isinstance(v, dict):
        for tk in v:
            for sk, sv in create_nested_kvl(v[tk], tk):
                yield '{}.{}'.format(k, sk), sv
    else:
        yield k, v

看起来很好而且紧凑。 E.g:

v = {'type1':'type1_val',
     'type2':'type2_val',
     'object': {
          'k1': 'val1',
          'k2': 'val2',
          'k3': {'k31': {
                     'k311': 'val311',
                     'k322': 'val322',
                     'k333': 'val333'
                     },
                'k32': 'val32',
                'k33': 'val33'}}}

import pprint
pprint.pprint(list(create_nested_kvl(v, 'base')))

发射

[('base.object.k3.k31.k311', 'val311'),
 ('base.object.k3.k31.k333', 'val333'),
 ('base.object.k3.k31.k322', 'val322'),
 ('base.object.k3.k33', 'val33'),
 ('base.object.k3.k32', 'val32'),
 ('base.object.k2', 'val2'),
 ('base.object.k1', 'val1'),
 ('base.type1', 'type1_val'),
 ('base.type2', 'type2_val')]

根据需要。

补充:在Python中,“快速”和“优雅”经常重合 - 但不是总是那么。特别是,递归稍慢,循环中全局变量的查找也是如此。所以,在这里,通过显式堆栈拉取所有通常的递归消除技巧,并查找提升,可以得到......:

def faster(v, k='', isinstance=isinstance):
    stack = [(k, v)]
    result = []
    push, pop = stack.append, stack.pop
    resadd = result.append
    fmt = '{}.{}'.format
    while stack:
        k, v = pop()
        if isinstance(v, dict):
            for tk, vtk in v.iteritems():
                push((fmt(k, tk), vtk))
        else:
            resadd((k, v))
    return result

...绝对不是那么优雅,但是...在我的笔记本电脑上,我的原始版本,加上最后一个list(),在给定的样本v上花费21.5微秒;这个更快的版本需要16.8微秒。如果保存那些4.7微秒(或者,更有意义地表示,原始运行时的22%)比清晰度和可维护性更重要,那么可以选择第二个版本并获得相同的结果(与通常的订购一样),这要快得多。

OP的“修订版”在示例v上仍然更快,部分原因是因为在{2}中使用%格式比在更优雅format中稍快一些,部分原因是因为{ {1}}比items略快(仅限Python 2);并且一些提升可能会进一步削减一些纳秒。