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

fas*_*uto 20 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
Run Code Online (Sandbox Code Playgroud)

这段代码有效,但它不是很优雅,我确信有更好的解决方案.

Ned*_*der 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
Run Code Online (Sandbox Code Playgroud)


MSe*_*ert 16

首先,我认为你的代码工作而不是不优雅.没有直接的理由不使用您提供的代码.

有一些事情可能会更好:

比较类型

您的代码包含以下行:

if type(dict_foo[field]) == dict:
Run Code Online (Sandbox Code Playgroud)

这肯定会有所改善.通常(另请参阅PEP8)您应该使用isinstance而不是比较类型:

if isinstance(dict_foo[field], dict)
Run Code Online (Sandbox Code Playgroud)

但是,True如果dict_foo[field]是子类,那么它也将返回dict.如果你不想那样,你也可以用is而不是==.这将略微(并且可能不明显)更快.

如果你还想允许任意类似dict的对象,你可以更进一步测试它是否是a collections.abc.MutableMapping.这将是Truefor dictdictsubslasses以及所有可显式实现该接口而没有子类化的可变映射,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
Run Code Online (Sandbox Code Playgroud)

原位修改和返回值

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

从字典中删除键

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

for key in keys_to_remove:
    try:
        del dict[key]
    except KeyError:
        pass
Run Code Online (Sandbox Code Playgroud)

这还有一个额外的好处,你不需要嵌套两个循环(这可能会更慢,特别是如果需要删除的键的数量非常长).

如果您不喜欢空except子句,您还可以使用:( contextlib.suppress需要Python 3.4+):

from contextlib import suppress

for key in keys_to_remove:
    with suppress(KeyError):
        del dict[key] 
Run Code Online (Sandbox Code Playgroud)

变量名称

我会重命名一些变量,因为它们只是描述性的,甚至是误导性的:

  • delete_keys_from_dict也许应该提到子句处理delete_keys_from_dict_recursive.

  • dict_del听起来像一个删除的字典.我倾向于喜欢这样的名字,dictionary或者dct因为函数名称已经描述了对字典做了什么.

  • 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)
Run Code Online (Sandbox Code Playgroud)

以及返回新对象的解决方案:

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
Run Code Online (Sandbox Code Playgroud)

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

  • 哇!这是一个令人印象深刻的答案。8 年前我问过这个问题,从那时起我学到了很多东西,但是我不知道 `contextlib.suppress` 或 `MutableMapping`。非常感谢@MSeifert! (3认同)

Mic*_*ner 12

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)

d = {'a': 10, 'b': [{'c': 10, 'd': 10, 'a': 10}, {'a': 10}], 'c': 1 }
delete_keys_from_dict(d, ['a', 'c']) # inplace deletion 
print(d)

>>> {'b': [{'d': 10}, {}]}
Run Code Online (Sandbox Code Playgroud)

该解决方案适用于dictlist定的嵌套dict. 输入to_delete可以是要删除的listofstr或单个str.

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

  • 这满足了我的需要,首选答案没有,因为它错过了列表的递归。问:为什么要退货?它就地工作。 (2认同)

Mah*_*emi 9

由于这个问题要求优雅,我会将我的通用解决方案提交给争论嵌套结构.首先,安装博尔顿工具包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'}
Run Code Online (Sandbox Code Playgroud)

简而言之,重映射实用程序是处理通常嵌套的真实数据结构的全功能但简洁的方法,甚至可以包含循环和特殊容器.

这个页面有更多的例子,包括使用Github API的更大对象的例子.

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

  • 这应该是正确的答案,因为OP要求一种“优雅”的方式 (2认同)