从嵌套词典中删除字段的优雅方法 [英] Elegant way to remove fields from nested dictionaries

查看:67
本文介绍了从嵌套词典中删除字段的优雅方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

此代码有效,但是它不是很好,我相信有更好的解决方案.

解决方案

首先,我认为您的代码有效,而不是 inlegant .没有立即的理由不使用您提供的代码.

有些事情可能会更好:

比较类型

您的代码包含以下行:

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

那肯定可以改善.通常(另请参见 PEP8 ),您应该使用isinstance而不是比较类型:

if isinstance(dict_foo[field], dict)

但是,如果dict_foo[field]dict的子类,则也会返回True.如果您不想这样做,也可以使用is代替==.这样会更快(也许不会引起注意).

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

这还有一个优点,您不需要嵌套两个循环(可能比较慢,尤其是在需要删除的键数非常长的情况下).

如果您不喜欢空的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(您只需多次遍历 即可),而不仅仅是列表. /p>

  • dict_foo,只是没有...

  • field也不是很合适,它是一个 key .

将它们放在一起:

正如我之前所说,我个人将就地修改字典,而再次返回字典.因此,我提出了两种解决方案,一种解决方案是就地修改它,但不返回任何内容,另一种解决方案是在删除键的情况下创建新字典.

就地修改的版本(非常类似于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

但是,它仅制作字典的副本,而其他值不作为副本返回,您可以轻松地将它们包装在

This code works, but it's not very elegant and I'm sure that there is a better solution.

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 isinstance instead of comparing types:

if isinstance(dict_foo[field], dict)

However that will also return True if dict_foo[field] is a subclass of dict. If you don't want that, you could also use is instead of ==. That will be marginally (and probably unnoticeable) faster.

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 True for dict and dict subclasses and for all mutable mappings that explicitly implement that interface without subclassing dict, for example 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 or return a new (modified) data structure. Just to mention a few examples: list.append, dict.clear, dict.update all 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_del line and let it implicitly return None, but 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 could be slower, especially if the number of keys that need to be removed is very long).

If you don't like empty except clauses you can also use: contextlib.suppress (requires 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_dict should probably mention the subdict-handling, maybe delete_keys_from_dict_recursive.

  • dict_del sounds like a deleted dict. I tend to prefer names like dictionary or dct because the function name already describes what is done to the dictionary.

  • lst_keys, same there. I'd probably use just keys there. If you want to be more specific something like keys_sequence would make more sense because it accepts any sequence (you just have to be able to iterate over it multiple times), not just lists.

  • dict_foo, just no...

  • field isn't really appropriate either, it's a key.

Putting it all together:

As I said before I personally would modify the dictionary in-place and not return 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):

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.

这篇关于从嵌套词典中删除字段的优雅方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆