Python中的递归函数:获取特定嵌套项列表的最佳方法

时间:2015-05-07 01:01:36

标签: python dictionary nested

我有一个嵌套字典树。这是一个小提取物,只是为了给你一个想法:

db = {
    'compatibility': {
        'style': {
            'path_to_file': 'compatibility/render/style.py',
            'checksum': {
                '0.0.3':'AAA55d796c25ad867bbcb8e0da4e48d17826e6f9fce',
                '0.0.2': '55d796c25ad867bbcb8e0da4e48d17826e6f9fe606',}}},
    'developer': {
        'render': {
            'installation': {
                'path_to_file': 'developer/render/installation.py',
                'checksum': {
                    '0.0.1': 'c1c0d4080e72292710ac1ce942cf59ce0e26319cf3'}},
            'tests': {
                'path_to_file': 'developer/render/test.py',
                'checksum': {
                    '0.0.1': 'e71173ac43ecd949fdb96cfb835abadb877a5233a36b115'}}}}}

我想获得嵌套在树中的所有字典模块的列表。这样我就能循环列表并测试每个文件的校验和(注意模块可能处于不同的级别,如上例所示)。

为了实现这一点,我编写了以下递归函数。我知道每个模块都有一个" path_to_file"和"校验和"键,所以我用它来测试dict是否是一个模块。请注意,我必须将递归函数包装在另一个包含列表的函数中,以便每次递归函数运行时都不会覆盖该列表。

def _get_modules_from_db(dictionary):
    def recursive_find(inner_dictionary):
        for k, v in inner_dictionary.iteritems():
            if (isinstance(v, dict) and
                    not sorted(v.keys()) == ['path_to_file', 'sha512sum']):
                recursive_find(v)
            else:
                leaves.append(v)
    leaves = []
    recursive_find(dictionary)
    return leaves

这种方法有效,但不得不包裹这个功能对我来说似乎很难看。那么,我在Stack Overflow上的专业人士的问题:

是否有更简单(或更好)的方法,你建议实现这一点,而不必包装功能?

3 个答案:

答案 0 :(得分:6)

首先,你需要包装该函数的唯一原因是因为你使recursive_find就地改变了leaves闭包单元而不是return它。有时候这是一个有用的性能优化(虽然经常是悲观),有时它不清楚如何做到这一点,但这一次它是微不足道的:

def _get_modules_from_db(dictionary):
    leaves = []
    for k, v in dictionary.iteritems():
        if (isinstance(v, dict) and
            not sorted(v.keys()) == ['path_to_file', 'sha512sum']):
            leaves.extend(_get_modules_from_db(v))
        else:
            leaves.append(v)
    return leaves

为了进一步改进:我可能会把它变成一个生成器(至少在3.3+中,yield from;在2.7中我可能会三思而后行。而且,当我们在它时,我会将键视图(在3.x中)或set(v)(在2.x中)与集合进行比较而不是进行不必要的排序(并且没有理由{ {1}}使用.keys()set),并使用sorted而不是!=not。而且,除非有充分的理由只接受实际的==dict子类,否则我要点击它或使用dict。所以:

collections.[abc.]Mapping

或者,将基本情况拉出来,这样你就可以直接在字符串上调用它:

def _get_modules_from_db(dictionary):
    for k, v in dictionary.items():
        if isinstance(v, Mapping) and v.keys() != {'path_to_file', 'sha512sum'}:
            yield from _get_modules_from_db(v)
        else:
            yield v

我认为这比你的可读性更强一点,它是6行而不是11行(尽管2.x版本是7行)。但是我没有看到你的版本出现任何问题。

如果您不确定如何将3.3+代码转换为2.7 / 3.2代码:

  • def _get_modules_from_db(d): if isinstance(d, Mapping) and d.keys() != {'path_to_file', 'sha512sum'}: for v in d.values(): yield from _get_modules_from_db(v) else: yield d 重写为yield from eggs
  • for egg in eggs: yield egg位于Mapping,而不是collections
  • 使用collections.abc代替set(v)
  • 可能使用v.keys()代替itervalues(仅限2.x)。

答案 1 :(得分:2)

我认为这种方法没有问题。你想要一个操纵一些全局状态的递归函数 - 这是一种非常合理的方法(内部函数在Python中并不常见)。

也就是说,如果你想避免使用嵌套函数,你可以添加一个默认参数:

def _get_modules_from_db(db, leaves=None):
    if leaves is None:
        leaves = []
    if not isinstance(db, dict):
        return leaves

    # Use 'in' check to avoid sorting keys and doing a list compare
    if 'path_to_file' in db and 'checksum' in db:
        leaves.append(db)
    else:
        for v in db.values():
            _get_modules_from_db(v, leaves)

    return leaves

答案 2 :(得分:1)

在我个人看来,嵌套功能很不错,但是这里的版本更简洁

from operator import add

def _get_modules_from_db(db):
  if 'path_to_file' in db and 'sha512sum' in db:
    return [db]
  return reduce(add, (_get_modules_from_db(db[m]) for m in db))