如何通过嵌套字典以pythonic方式按键过滤

时间:2015-07-29 20:15:34

标签: python dictionary filter dictionary-comprehension

尝试过滤嵌套字典。我的解决方案很笨重,希望看看是否有更好的方法使用了解。只对这个例子的字典和列表感兴趣。

_dict_key_filter()将过滤嵌套字典的键或嵌套字典列表。任何不在obj_filter中的内容都将在所有嵌套级别上被忽略。

obj:可以是字典或字典列表。

obj_filter:必须是过滤值列表

def _dict_key_filter(self, obj, obj_filter):
    if isinstance(obj, dict):
        retdict = {}
        for key, value in obj.iteritems():
            if key in obj_filter:
                retdict[key] = copy.deepcopy(value)
            elif isinstance(value, (dict, list)):
                child = self._dict_key_filter(value, obj_filter)
                if child:
                    retdict[key] = child
        return retdict if retdict else None
    elif isinstance(obj, list):
        retlist = []
        for value in list:
            child = self._dict_key_filter(value, obj_filter)
            if child:
                retlist.append(child)
        return retlist if retlist else None
    else:
        return None

Example#
dict1 = {'test1': {'test2':[1,2]}, 'test3': [{'test6': 2}, 
         {'test8': {'test9': 23}}], 'test4':{'test5': 5}}

filter = ['test5' , 'test9']

return = _dict_key_filter(dict1, filter)

return value would be {'test3': [{'test8': {'test9': 23}}], 'test4': {'test5': 5}}

1 个答案:

答案 0 :(得分:1)

这是一个非常老的问题。我最近遇到了类似的问题。

这也许很明显,但是您正在处理一棵树,其中每个节点都有任意数量的子代。您想要剪切不包含某些项目的子树作为节点(不是叶子)。为此,您使用了自定义DFS:main函数返回子树或None。如果值为None,则“剪切”分支。

首先,函数dict_key_filter返回一个(非空)dict,一个(非空)listNone(如果未在其中找到过滤键)分支。 为了降低复杂度,在每种情况下都可以返回sequence:如果未找到过滤键,则返回空序列;如果仍在搜索或找到树的叶子,则返回非空序列。您的代码如下:

def dict_key_filter(obj, obj_filter):
    if isinstance(obj, dict):
        retdict = {}
        ...
        return retdict # empty or not
    elif isinstance(obj, list):
        retlist = []
        ...
        return retlist # empty or not
    else:
        return [] # obvioulsy empty

这是容易的部分。现在我们必须填充点。

list

让我们从list案例开始,因为它很容易重构:

retlist = []
for value in obj:
    child = dict_key_filter0(value, obj_filter)
    if child:
        retlist.append(child)

我们可以将其转换为简单的列表理解:

retlist = [dict_key_filter(value, obj_filter) for value in obj if dict_key_filter(value, obj_filter)]

缺点是dict_key_filter被评估两次。我们可以通过一些技巧来避免这种情况(请参阅https://stackoverflow.com/a/15812866):

retlist = [subtree for subtree in (dict_key_filter(value, obj_filter) for value in obj) if subtree]

内部表达式(dict_key_filter(value, obj_filter) for value in obj)是一个生成器,每个值调用一次dict_key_filter。但是,如果我们构建dict_key_filter的闭包,我们甚至可以做得更好:

def dict_key_filter(obj, obj_filter):
    def inner_dict_key_filter(obj): return dict_key_filter(obj, obj_filter)

    ...

    retlist = list(filter(len, map(inner_dict_key_filter, obj)))

现在我们进入了函数世界:mapinner_dict_key_filter应用于列表的每个元素,然后子树将被过滤以排除空子树({{1}是正确的,如果{{1 }}不为空。现在,代码如下:

len(subtree)

如果您熟悉函数式编程,那么subtree的情况是可读的(不像Haskell那样可读,但仍然可读)。

def dict_key_filter(obj, obj_filter): def inner_dict_key_filter(obj): return dict_key_filter(obj, obj_filter) if isinstance(obj, dict): retdict = {} ... return retdict elif isinstance(obj, list): return list(filter(len, map(inner_dict_key_filter, obj))) else: return []

我不会忘记您的问题中的list标签。第一个想法是创建一个函数以返回分支的完整副本或DFS其余部分的结果。

dict

dictionary-comprehension情况一样,我们暂时不拒绝空白的def build_subtree(key, value): if key in obj_filter: return copy.deepcopy(value) # keep the branch elif isinstance(value, (dict, list)): return inner_dict_key_filter(value) # continue to search return [] # just an orphan value here

list

我们现在有一个完美的字典理解案例:

subtree

同样,我们使用小技巧来避免两次计算值:

retdict = {}
for key, value in obj.items():
    retdict[key] = build_subtree(key, value)

但是这里我们有一个小问题:上面的代码并不等同于原始代码。如果值为retdict = {key: build_subtree(key, value) for key, value in obj.items() if build_subtree(key, value)} 怎么办?在原始版本中,我们有retdict = {key:subtree for key, subtree in ((key, build_subtree(key, value)) for key, value in obj.items()) if subtree} ,但在新版本中,我们什么都没有。 0值被评估为false并被过滤。然后,该字典可能会变空,并且我们错误地剪切了该分支。我们需要进行另一项测试以确保我们要删除一个值:如果它是空列表或字典,则将其删除,否则保留它:

retdict[key] = copy.deepcopy(0)

也就是说:

0

如果您还记得一些逻辑(https://en.wikipedia.org/wiki/Truth_table#Logical_implication),可以将其解释为:如果def to_keep(subtree): return not (isinstance(subtree, (dict, list)) or len(subtree) == 0) 是字典或列表,则它不能为空。

让我们放在一起:

 def to_keep(subtree): return not isinstance(subtree, (dict, list)) or subtree

我不知道这是否更pythonic,但是对我来说似乎更清楚。

subtree