Python 从嵌套字典中删除字段的优雅方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3405715/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Elegant way to remove fields from nested dictionaries
提问by fasouto
I had to remove some fields from a dictionary, the keys for those fields are on a list. So I wrote this function:
我不得不从字典中删除一些字段,这些字段的键在列表中。所以我写了这个函数:
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
This code works, but it's not very elegant and I'm sure that there is a better solution.
这段代码有效,但不是很优雅,我相信有更好的解决方案。
采纳答案by MSeifert
First of, I think your code is working and not inelegant. There's no immediate reason not to use the code you presented.
首先,我认为你的代码是有效的,而不是不优雅的。没有直接的理由不使用您提供的代码。
There are a few things that could be better though:
不过,有几件事可能会更好:
Comparing the type
比较类型
Your code contains the line:
您的代码包含以下行:
if type(dict_foo[field]) == dict:
That can be definitely improved. Generally (see also PEP8) you should use isinstanceinstead of comparing types:
那肯定可以改进。通常(另见PEP8)您应该使用isinstance而不是比较类型:
if isinstance(dict_foo[field], dict)
However that will also return Trueif dict_foo[field]is a subclass of dict. If you don't want that, you could also use isinstead of ==. That will be marginally (and probably unnoticeable) faster.
但是,True如果dict_foo[field]是dict. 如果你不想那样,你也可以使用is代替==。这将略微(并且可能不会引起注意)更快。
If you also want to allow arbitary dict-like objects you could go a step further and test if it's a collections.abc.MutableMapping. That will be Truefor dictand dictsubclasses and for all mutable mappings that explicitly implement that interface without subclassing dict, for example UserDict:
如果您还想允许任意类似 dict 的对象,您可以更进一步并测试它是否是collections.abc.MutableMapping. 这将是Truefordict和dictsubclasses 以及所有显式实现该接口而无需子类化的可变映射,dict例如UserDict:
>>> 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
Inplace modification and return value
就地修改和返回值
Typically functions either modify a data structure inplace orreturn a new (modified) data structure. Just to mention a few examples: list.append, dict.clear, dict.updateall modify the data structure inplace and return None. That makes it easier to keep track what a function does. However that's not a hard rule and there are always valid exceptions from this rule. However personally I think a function like this doesn't need to be an exception and I would simply remove the return dict_delline and let it implicitly return None, but YMMV.
通常,函数要么就地修改数据结构,要么返回新的(修改过的)数据结构。仅举几个例子:list.append,dict.clear,dict.update都修改数据结构就地和return None。这样可以更轻松地跟踪函数的作用。然而,这不是一个硬性规则,并且此规则始终存在有效的例外情况。但是我个人认为这样的函数不需要是一个例外,我会简单地删除该return dict_del行并让它隐式返回None,但是 YMMV.
Removing the keys from the dictionary
从字典中删除键
You copied the dictionary to avoid problems when you remove key-value pairs during the iteration. However, as already mentioned by another answer you could just iterate over the keys that should be removed and try to delete them:
您复制了字典以避免在迭代期间删除键值对时出现问题。但是,正如另一个答案已经提到的,您可以遍历应该删除的键并尝试删除它们:
for key in keys_to_remove:
try:
del dict[key]
except KeyError:
pass
That has the additional advantage that you don't need to nest two loops (which couldbe slower, especially if the number of keys that need to be removed is very long).
这还有一个额外的好处,你不需要嵌套两个循环(这可能会更慢,特别是如果需要删除的键数很长)。
If you don't like empty exceptclauses you can also use: contextlib.suppress(requires Python 3.4+):
如果你不喜欢空except子句,你也可以使用:(contextlib.suppress需要 Python 3.4+):
from contextlib import suppress
for key in keys_to_remove:
with suppress(KeyError):
del dict[key]
Variable names
变量名
There are a few variables I would rename because they are just not descriptive or even misleading:
我会重命名一些变量,因为它们没有描述性甚至误导性:
delete_keys_from_dictshould probably mention the subdict-handling, maybedelete_keys_from_dict_recursive.dict_delsounds like a deleted dict. I tend to prefer names likedictionaryordctbecause the function name already describes what is done to the dictionary.lst_keys, same there. I'd probably use justkeysthere. If you want to be more specific something likekeys_sequencewould make more sense because it accepts anysequence(you just have to be able to iterate over it multiple times), not just lists.dict_foo, just no...fieldisn't really appropriate either, it's a key.
delete_keys_from_dict可能应该提到 subdict 处理,也许delete_keys_from_dict_recursive.dict_del听起来像一个删除的字典。我倾向于喜欢这样的名字,dictionary或者dct因为函数名已经描述了对字典所做的事情。lst_keys,那里一样。我可能会在keys那里使用。如果你想要更具体的东西keys_sequence更有意义,因为它接受任何sequence(你只需要能够多次迭代),而不仅仅是列表。dict_foo, 就是不行...field也不是很合适,它是一个key。
Putting it all together:
把它们放在一起:
As I said before I personally would modify the dictionary in-place and notreturn the dictionary again. Because of that I present two solutions, one that modifies it in-place but doesn't return anything and one that creates a new dictionary with the keys removed.
正如我之前所说,我个人会就地修改字典,不再返回字典。因此,我提出了两种解决方案,一种是就地修改但不返回任何内容,另一种是创建一个删除键的新字典。
The version that modifies in-place (very much like Ned Batchelders solution):
就地修改的版本(非常像 Ned Batchelders 解决方案):
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)
And the solution that returns a new object:
以及返回新对象的解决方案:
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
However it only makes copies of the dictionaries, the other values are not returned as copy, you could easily wrap these in copy.deepcopy(I put a comment in the appropriate place of the code) if you want that.
然而,它只制作字典的副本,其他值不会作为副本返回,如果需要,您可以轻松地将它们包装起来copy.deepcopy(我在代码的适当位置添加了注释)。
回答by Ned Batchelder
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
回答by John La Rooy
Since you already need to loop through every element in the dict, I'd stick with a single loop and just make sure to use a set for looking up the keys to delete
由于您已经需要遍历字典中的每个元素,因此我会坚持使用单个循环,并确保使用集合来查找要删除的键
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
回答by Mahmoud Hashemi
Since the question requested an elegant way, I'll submit my general-purpose solution to wrangling nested structures. First, install the boltons utility packagewith pip install boltons, then:
由于这个问题需要一种优雅的方式,我将提交我的通用解决方案来解决嵌套结构的问题。首先,安装博尔顿工具包用pip install boltons,则:
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'}
In short, the remap utilityis a full-featured, yet succinct approach to handling real-world data structures which are often nested, and can even contain cycles and special containers.
简而言之,remap 实用程序是一种功能齐全但简洁的方法来处理通常嵌套的现实世界数据结构,甚至可以包含循环和特殊容器。
This pagehas many more examples, including ones working with much larger objects from Github's API.
此页面有更多示例,包括使用 Github API 中更大对象的示例。
It's pure-Python, so it works everywhere, and is fully tested in Python 2.7 and 3.3+. Best of all, I wrote it for exactly cases like this, so if you find a case it doesn't handle, you can bug me to fix it right here.
它是纯 Python,因此可以在任何地方使用,并且在 Python 2.7 和 3.3+ 中进行了全面测试。最重要的是,我写的正是针对这样的情况,所以如果你发现它无法处理的情况,你可以在这里找我修复它。
回答by gies0r
Using the awesome code from thispost and add a small statement:
使用这篇文章中的精彩代码并添加一个小声明:
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}
回答by Kingz
I think the following is more elegant:
我认为以下更优雅:
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}
回答by Michael Dorner
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}, {}]}
This solution works for dictand listin a given nested dict. The input to_deletecan be a listof strto be deleted or a single str.
该解决方案适用于dict并list在给定的嵌套dict。所述输入to_delete可以是一个list的str要被删除或单个str。
Plese note, that if you remove the only key in a dict, you will get an empty dict.
请注意,如果您删除 a 中唯一的键dict,您将得到一个空的dict.
回答by Dill
this works with dicts containing Iterables (list, ...) that may contain dict. Python 3. For Python 2 unicodeshould also be excluded from the iteration. Also there may be some iterables that don't work that I'm not aware of. (i.e. will lead to inifinite recursion)
这适用于dict包含可能包含Iterables ( list, ...) 的s dict。Python 3. 对于 Python 2unicode也应该从迭代中排除。也可能有一些我不知道的无效的迭代。(即会导致无限递归)
from collections.abc import Iterable
def deep_omit(d, keys):
if isinstance(d, dict):
for k in keys:
d.pop(k, None)
for v in d.values():
deep_omit(v, keys)
elif isinstance(d, Iterable) and not isinstance(d, str):
for e in d:
deep_omit(e, keys)
return d

