如何以 pythonic 方式通过嵌套字典按键过滤
How to filter by keys through a nested dictionary in a pythonic way
尝试过滤嵌套字典。我的解决方案很笨拙,希望看看是否有更好的方法使用理解。只对这个例子的字典和列表感兴趣。
_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}}
这是一个非常古老的问题。我最近遇到了类似的问题。
这可能很明显,但您正在处理一棵树,其中每个节点都有任意数量的子节点。您想要将不包含某些项目的子树切割为节点(不是叶子)。为此,您正在使用自定义 DFS:主函数 return 是一个子树或 None
。如果值为 None
那么您 "cut" 分支。
首先,函数dict_key_filter
returns a(非空)dict
,a(非空)list
or None
if no在分支中找不到过滤键。
为了降低复杂性,您可以在每种情况下 return a 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
被计算了两次。我们可以通过一个小技巧来避免这种情况(参见):
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)))
现在我们处于函数世界:map
将 inner_dict_key_filter
应用于列表的每个元素,然后过滤子树以排除空子树(len(subtree)
为真当且仅当 subtree
不为空)。现在,代码如下所示:
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
案例是可读的(不像 Haskell 那样可读,但仍然可读)。
dict
案例
我没有忘记您问题中的 dictionary-comprehension
标签。第一个想法是创建一个函数 return 分支的完整副本或 DFS 其余部分的结果。
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
怎么办?在原始版本中,我们有 retdict[key] = copy.deepcopy(0)
但在新版本中我们什么都没有。 0
值被评估为 false 并被过滤。然后 dict 可能会变空,我们错误地切断了分支。我们需要另一个测试来确定我们要删除一个值:如果它是一个空列表或字典,则将其删除,否则保留它:
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
如果您还记得一点逻辑 (https://en.wikipedia.org/wiki/Truth_table#Logical_implication),您可以将其解释为:如果 subtree
是字典或列表,则它不能为空。
让我们把各个部分放在一起:
def dict_key_filter(obj, obj_filter):
def inner_dict_key_filter(obj): return dict_key_filter(obj, obj_filter)
def to_keep(subtree): return not isinstance(subtree, (dict, list)) or subtree
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
if isinstance(obj, dict):
key_subtree_pairs = ((key, build_subtree(key, value)) for key, value in obj.items())
return {key:subtree for key, subtree in key_subtree_pairs if to_keep(subtree)}
elif isinstance(obj, list):
return list(filter(to_keep, map(inner_dict_key_filter, obj)))
return []
我不知道这是否更像 pythonic,但对我来说似乎更清楚。
dict1 = {
'test1': { 'test2':[1,2] },
'test3': [
{'test6': 2},
{
'test8': { 'test9': 23 }
}
],
'test4':{'test5': 0}
}
obj_filter = ['test5' , 'test9']
print (dict_key_filter(dict1, obj_filter))
# {'test3': [{'test8': {'test9': 23}}], 'test4': {'test5': 0}}
尝试过滤嵌套字典。我的解决方案很笨拙,希望看看是否有更好的方法使用理解。只对这个例子的字典和列表感兴趣。
_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}}
这是一个非常古老的问题。我最近遇到了类似的问题。
这可能很明显,但您正在处理一棵树,其中每个节点都有任意数量的子节点。您想要将不包含某些项目的子树切割为节点(不是叶子)。为此,您正在使用自定义 DFS:主函数 return 是一个子树或 None
。如果值为 None
那么您 "cut" 分支。
首先,函数dict_key_filter
returns a(非空)dict
,a(非空)list
or None
if no在分支中找不到过滤键。
为了降低复杂性,您可以在每种情况下 return a 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
被计算了两次。我们可以通过一个小技巧来避免这种情况(参见):
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)))
现在我们处于函数世界:map
将 inner_dict_key_filter
应用于列表的每个元素,然后过滤子树以排除空子树(len(subtree)
为真当且仅当 subtree
不为空)。现在,代码如下所示:
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
案例是可读的(不像 Haskell 那样可读,但仍然可读)。
dict
案例
我没有忘记您问题中的 dictionary-comprehension
标签。第一个想法是创建一个函数 return 分支的完整副本或 DFS 其余部分的结果。
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
怎么办?在原始版本中,我们有 retdict[key] = copy.deepcopy(0)
但在新版本中我们什么都没有。 0
值被评估为 false 并被过滤。然后 dict 可能会变空,我们错误地切断了分支。我们需要另一个测试来确定我们要删除一个值:如果它是一个空列表或字典,则将其删除,否则保留它:
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
如果您还记得一点逻辑 (https://en.wikipedia.org/wiki/Truth_table#Logical_implication),您可以将其解释为:如果 subtree
是字典或列表,则它不能为空。
让我们把各个部分放在一起:
def dict_key_filter(obj, obj_filter):
def inner_dict_key_filter(obj): return dict_key_filter(obj, obj_filter)
def to_keep(subtree): return not isinstance(subtree, (dict, list)) or subtree
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
if isinstance(obj, dict):
key_subtree_pairs = ((key, build_subtree(key, value)) for key, value in obj.items())
return {key:subtree for key, subtree in key_subtree_pairs if to_keep(subtree)}
elif isinstance(obj, list):
return list(filter(to_keep, map(inner_dict_key_filter, obj)))
return []
我不知道这是否更像 pythonic,但对我来说似乎更清楚。
dict1 = {
'test1': { 'test2':[1,2] },
'test3': [
{'test6': 2},
{
'test8': { 'test9': 23 }
}
],
'test4':{'test5': 0}
}
obj_filter = ['test5' , 'test9']
print (dict_key_filter(dict1, obj_filter))
# {'test3': [{'test8': {'test9': 23}}], 'test4': {'test5': 0}}