【问题标题】:Recursive function in Python: Best way to get a list of specific nested itemsPython中的递归函数:获取特定嵌套项列表的最佳方法
【发布时间】:2015-07-17 09:12:16
【问题描述】:

我有一棵嵌套字典树。这是一个小摘录,只是为了给你一个想法:

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”和“checksum”键,所以我用它来测试字典是否是一个模块。请注意,我必须将递归函数包装在另一个保存列表的函数中,这样每次递归函数运行时列表就不会被覆盖。

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 专业人士的问题:

您是否推荐更简单(或更好)的方法来实现这一点而无需包装函数?

【问题讨论】:

    标签: python dictionary nested


    【解决方案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))
    

    【讨论】:

    • 如果遇到非 dict 对象(不在示例数据库中,但 OP 的代码有针对它的防御),它可能无法停止递归。此外,它迭代的是键,而不是值。
    • 我已经更改了你提到的错误。所描述的 dict 只存储字符串——没有理由过度概括问题。
    • 'path_to_file' in db and 'sha512sum' in db 与那些 only 键不同。特别是因为 db 可以是字符串;通过使用 in db 作为您希望永远不会匹配的子字符串搜索来隐藏它有点 hacky,但如果您不确定它永远不会匹配它是完全错误的。
    【解决方案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
    

    【讨论】:

    • nneonneo,默认参数是我最初寻找的,谢谢!但是@abarnert 用生成器推荐让我大开眼界,所以我会接受他的回答!
    【解决方案3】:

    首先,您需要包装函数的唯一原因是因为您正在使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 中)与一组进行比较,而不是进行不必要的排序(并且没有理由 .keys()使用setsorted),并使用!= 而不是not==。而且,除非有充分的理由只接受 dictdict 子类,否则我要么直接输入它,要么使用 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
    

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

    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
    

    我认为这比您所拥有的更具可读性,它是 6 行而不是 11 行(尽管 2.x 版本是 7 行)。但我看不出你的版本有什么问题。


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

    • yield from eggs重写为for egg in eggs: yield egg
    • Mappingcollections,而不是 collections.abc
    • 使用set(v) 而不是v.keys()
    • 可能使用itervalues 而不是values(仅限2.x)。

    【讨论】:

    • 赞成:我更喜欢生成器 - 比使用列表更优雅。
    • @StefanPochmann:对不起;实际上,我为同一事物使用了 三个 名称,而不仅仅是两个,因为在没有测试的情况下复制和粘贴……现在应该修复了。
    • 谢谢@abarnert,我想我可以在 Python 2.7 中使用 v.viewkeys() 而不是 set(v) 对吧?
    • @chrisfromlocalgroup:是的,应该可以; viewkeys 只是 3.2 的 keys 的反向移植。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-15
    • 1970-01-01
    • 2019-05-05
    • 2016-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多