Elegant way to remove fields from nested dictionaries
我必须从字典中删除一些字段,这些字段的键在列表中。所以我写了这个函数:
1 2 3 4 5 6 7 8 9 10 11 12 | def delete_keys_from_dict(dict_del, lst_keys): """ Delete the keys present in lst_keys from the dictionary. Loops recursively over nested dictionaries. """ dict_foo = dict_del.copy() #Used as iterator to avoid the 'DictionaryHasChanged' error for field in dict_foo.keys(): if field in lst_keys: del dict_del[field] if type(dict_foo[field]) == dict: delete_keys_from_dict(dict_del[field], lst_keys) return dict_del |
这段代码可以工作,但它不是很优雅,我相信有更好的解决方案。
1 2 3 4 5 6 7 8 9 10 11 | def delete_keys_from_dict(dict_del, lst_keys): for k in lst_keys: try: del dict_del[k] except KeyError: pass for v in dict_del.values(): if isinstance(v, dict): delete_keys_from_dict(v, lst_keys) return dict_del |
首先,我认为您的代码是有效的,而且不是不雅的。没有立即的理由不使用您提供的代码。
不过,还有一些事情可以做得更好:
比较类型您的代码包含行:
1 | if type(dict_foo[field]) == dict: |
这绝对可以改进。一般情况下(另见PEP8),您应该使用
1 | if isinstance(dict_foo[field], dict) |
但是,如果
如果你还想允许任意听写的对象,你可以更进一步,测试它是不是一个
1 2 3 4 5 6 7 8 | >>> from collections import MutableMapping >>> # from UserDict import UserDict # Python 2.x >>> from collections import UserDict # Python 3.x - 3.6 >>> # from collections.abc import MutableMapping # Python 3.7+ >>> isinstance(UserDict(), MutableMapping) True >>> isinstance(UserDict(), dict) False |
就地修改返回值
通常函数要么就地修改数据结构,要么返回新的(修改过的)数据结构。仅举几个例子:
您复制了字典以避免在迭代过程中删除键值对时出现问题。但是,正如另一个答案已经提到的,您可以迭代应该删除的键,并尝试删除它们:
1 2 3 4 5 | for key in keys_to_remove: try: del dict[key] except KeyError: pass |
这还有一个额外的优点,即您不需要嵌套两个循环(这可能会比较慢,尤其是当需要删除的键的数量非常长时)。
如果不喜欢空的
1 2 3 4 5 | from contextlib import suppress for key in keys_to_remove: with suppress(KeyError): del dict[key] |
变量名
我将重命名一些变量,因为它们不具有描述性,甚至不具有误导性:
delete_keys_from_dict 可能会提到分包的处理,可能delete_keys_from_dict_recursive 。dict_del 听起来像是删除的dict。我倾向于使用dictionary 或dct 这样的名称,因为函数名已经描述了对字典所做的操作。lst_keys ,那里也一样。我可能只在那里使用keys 。如果你想更具体一些,比如keys_sequence 会更有意义,因为它接受任何sequence (你只需要多次迭代),而不仅仅是列表。dict_foo ,只是不…field 也不太合适,它是一把钥匙。
把它们放在一起:
正如我之前所说,我会亲自修改字典的位置,不再返回字典。因此,我提出了两个解决方案,一个是在适当的地方修改它,但不返回任何内容,另一个是创建一个删除了键的新字典。
就地修改的版本(非常类似于Ned BatchElders解决方案):
1 2 3 4 5 6 7 8 9 10 | from collections import MutableMapping from contextlib import suppress def delete_keys_from_dict(dictionary, keys): for key in keys: with suppress(KeyError): del dictionary[key] for value in dictionary.values(): if isinstance(value, MutableMapping): delete_keys_from_dict(value, keys) |
以及返回新对象的解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 | from collections import MutableMapping def delete_keys_from_dict(dictionary, keys): keys_set = set(keys) # Just an optimization for the"if key in keys" lookup. modified_dict = {} for key, value in dictionary.items(): if key not in keys_set: if isinstance(value, MutableMapping): modified_dict[key] = delete_keys_from_dict(value, keys_set) else: modified_dict[key] = value # or copy.deepcopy(value) if a copy is desired for non-dicts. return modified_dict |
但是,它只复制字典,其他值不会作为副本返回,如果需要的话,您可以很容易地将它们包装在
由于这个问题要求一种优雅的方式,我将把我的通用解决方案提交给正在争论的嵌套结构。首先,用
1 2 3 4 5 6 7 8 9 10 11 | from boltons.iterutils import remap data = {'one': 'remains', 'this': 'goes', 'of': 'course'} bad_keys = set(['this', 'is', 'a', 'list', 'of', 'keys']) drop_keys = lambda path, key, value: key not in bad_keys clean = remap(data, visit=drop_keys) print(clean) # Output: {'one': 'remains'} |
简言之,remap实用程序是一种功能齐全但简洁的处理现实数据结构的方法,这些数据结构通常是嵌套的,甚至可以包含循环和特殊容器。
这个页面有更多的例子,包括使用Github的API中更大的对象的例子。
它是纯Python,所以它在任何地方都可以工作,并且在Python2.7和3.3+中进行了全面的测试。最重要的是,我是为这样的案件写的,所以如果你发现一个案件它不能处理,你可以让我纠正它就在这里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | def delete_keys_from_dict(d, to_delete): if isinstance(to_delete, str): to_delete = [to_delete] if isinstance(d, dict): for single_to_delete in set(to_delete): if single_to_delete in d: del d[single_to_delete] for k, v in d.items(): delete_keys_from_dict(v, to_delete) elif isinstance(d, list): for i in d: delete_keys_from_dict(i, to_delete) return d d = {'a': 10, 'b': [{'c': 10, 'd': 10, 'a': 10}, {'a': 10}], 'c': 1 } delete_keys_from_dict(d, ['a', 'c']) >>> {'b': [{'d': 10}, {}]} |
此解决方案适用于给定嵌套的
请注意,如果删除
因为您已经需要循环访问dict中的每个元素,所以我只使用一个循环,并确保使用一个集合来查找要删除的键
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def delete_keys_from_dict(dict_del, the_keys): """ Delete the keys present in the lst_keys from the dictionary. Loops recursively over nested dictionaries. """ # make sure the_keys is a set to get O(1) lookups if type(the_keys) is not set: the_keys = set(the_keys) for k,v in dict_del.items(): if k in the_keys: del dict_del[k] if isinstance(v, dict): delete_keys_from_dict(v, the_keys) return dict_del |
我认为以下内容更优雅:
1 2 3 4 | def delete_keys_from_dict(dict_del, lst_keys): if not isinstance(dict_del, dict): return dict_del return {key:value for key,value in ((key, delete_keys_from_dict(value)) for key,value in dict_del.items()) if key not in lst_keys} |
使用这篇文章中令人敬畏的代码并添加一个小语句:
1 2 3 4 5 6 | def remove_fields(self, d, list_of_keys_to_remove): if not isinstance(d, (dict, list)): return d if isinstance(d, list): return [v for v in (self.remove_fields(v, list_of_keys_to_remove) for v in d) if v] return {k: v for k, v in ((k, self.remove_fields(v, list_of_keys_to_remove)) for k, v in d.items()) if k not in list_of_keys_to_remove} |