从嵌套字典中删除字段的优雅方法

时间:2010-08-04 12:58:48

标签: python dictionary

我不得不从字典中删除一些字段,这些字段的键位于列表中。所以我写了这个函数:

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

此代码有效,但不是很优雅,我确信有更好的解决方案。

9 个答案:

答案 0 :(得分:18)

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 :(得分:11)

首先,我认为您的代码正在运行,而不是不优雅。没有直接的理由不使用您提供的代码。

但有些事情可能会更好:

比较类型

您的代码包含以下行:

if type(dict_foo[field]) == dict:

这可以肯定得到改善。通常(另请参阅PEP8)您应该使用isinstance而不是比较类型:

if isinstance(dict_foo[field], dict)

但是如果Truedict_foo[field]的子类,那么它也会返回dict。如果您不想要,也可以使用is代替==。这将是勉强(并且可能不明显)更快。

如果您还想允许任意类似dict的对象,您可以更进一步测试它是否为collections.abc.MutableMapping。对于Truedict子类以及明确实现该接口而没有子类化dict的所有可变映射,这将是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

原地修改和返回值

通常,函数可以在场内修改数据结构返回一个新的(修改过的)数据结构。仅举几个例子:list.appenddict.cleardict.update都会修改到位的数据结构和return None。这样可以更容易地跟踪函数的功能。然而,这不是一个硬性规则,并且此规则始终存在有效的例外情况。不过我个人认为这样的函数不需要是一个例外,我只需删除return dict_del行并让它隐式返回None,但是YMMV。

从字典中删除密钥

您复制了字典,以避免在迭代过程中删除键值对时出现问题。但是,正如另一个答案已经提到的那样,您可以迭代应该删除的键并尝试删除它们:

for key in keys_to_remove:
    try:
        del dict[key]
    except KeyError:
        pass

这还有一个额外的好处,就是你不需要嵌套两个循环(可能更慢,特别是如果需要删除的密钥数量非常长)。 / p>

如果你不喜欢空的except条款,你也可以使用:contextlib.suppress(需要Python 3.4 +):

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听起来像是一个删除的字典。我倾向于喜欢dictionarydct之类的名称,因为函数名称已经描述了对字典所做的操作。

  • lst_keys,同样在那里。我可能只在那里使用keys。如果你想更具体一些像keys_sequence这样的东西会更有意义,因为它接受任何sequence(你只需要能够多次迭代多次),而不是只是列出。

  • dict_foo,只是没有......

  • field也不合适,它是

全部放在一起:

正如我之前所说的,我个人会在原地修改字典而再次返回字典。因此,我提出了两个解决方案,一个在原地修改它但没有返回任何内容的解决方案,另一个解决了删除键的新字典。

就地修改的版本(非常像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)

返回新对象的解决方案:

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

然而,它只会复制字典,其他值不会作为副本返回,如果你愿意的话,你可以轻松地将它们包装在copy.deepcopy中(我在代码的适当位置放置注释)。

答案 2 :(得分:9)

由于问题要求优雅,我会将我的通用解决方案提交给争论嵌套结构。首先,使用pip install boltons安装boltons utility package,然后:

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'}

简而言之,the remap utility是处理通常嵌套的真实数据结构的全功能但简洁的方法,甚至可以包含循环和特殊容器。

This page还有更多示例,包括使用Github API中更大对象的示例。

它是纯Python,因此它可以在任何地方使用,并且在Python 2.7和3.3+中进行了全面测试。最重要的是,我为这样的情况编写了它,所以如果你找到一个它无法处理的情况,你可以告诉我修复它right here

答案 3 :(得分:2)

由于你已经需要遍历dict中的每个元素,我会坚持使用一个循环,并确保使用一个集合来查找要删除的键

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

答案 4 :(得分:2)

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中的listdict。输入to_delete可以是要删除的list中的str,也可以是单个str

请注意,如果您删除dict中的唯一键,则会得到一个空的dict

答案 5 :(得分:1)

我认为以下更优雅:

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}

答案 6 :(得分:0)

使用来自this帖子的精彩代码并添加一个小语句:

    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}

答案 7 :(得分:0)

这适用于包含dict的{​​{1}}个{Iterable,...)。 Python3。对于Python 2 list也应从迭代中排除。也可能有一些我不知道的无法使用的可迭代项。 (即将导致无限递归)

dict

答案 8 :(得分:0)

我来这里是为了寻找从深层嵌套的Python3字典中删除键的解决方案,所有解决方案似乎都有些复杂。

这是从嵌套或平面字典中删除键的单行代码:

nested_dict = {
    "foo": {
        "bar": {
            "foobar": {},
            "shmoobar": {}
        }
    }
}

>>> {'foo': {'bar': {'foobar': {}, 'shmoobar': {}}}}

nested_dict.get("foo", {}).get("bar", {}).pop("shmoobar", None)

>>> {'foo': {'bar': {'foobar': {}}}}

我使用.get()来获取KeyError,并且我还提供了空dict作为默认值,直到链的末尾。我为最后一个元素做pop(),并提供None作为默认值,以避免KeyError。