优雅地从嵌套字典中移除字段
我需要从一个字典中删除一些字段,而这些字段的键在一个列表里。所以我写了这个函数:
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
这个代码可以运行,但看起来不太优雅,我相信一定有更好的解决办法。
11 个回答
因为这个问题想要一个优雅的解决方案,所以我来分享一个通用的方法来处理嵌套结构。首先,你需要安装一个叫做 boltons 的工具包,可以通过命令 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'}
简单来说,remap 工具 是一个功能强大且简洁的方法,专门用来处理现实中的数据结构,这些结构通常是嵌套的,甚至可能包含循环和特殊容器。
这个页面有很多更多的例子,包括一些处理来自Github API的更大对象的例子。
它是纯Python写的,所以在任何地方都能用,并且在Python 2.7和3.3以上的版本中经过了全面测试。最棒的是,我就是为了像这样的情况写的这个工具,所以如果你发现它不能处理某种情况,可以在 这里找我反馈,我会帮你修复的。
当然可以!请看下面的内容:
在编程中,有时候我们需要让程序在特定的条件下执行某些操作。这就像给程序设定了一些规则,只有当这些规则被满足时,程序才会做出反应。
比如说,你可能希望在用户点击一个按钮的时候,程序才会显示一条消息。这个过程就叫做“事件处理”。简单来说,就是程序在等待某个事件发生,然后根据这个事件来决定接下来要做什么。
在代码中,我们通常会使用一些特定的语法来设置这些事件和相应的操作。这样,当事件发生时,程序就会自动执行我们预先设定好的指令。
希望这个解释能帮助你更好地理解事件处理的概念!
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
首先,我觉得你的代码是可以工作的,并不是不优雅。没有什么理由不使用你提供的代码。
不过,有一些地方可以改进:
比较类型
你的代码中有一行:
if type(dict_foo[field]) == dict:
这部分是可以改进的。一般来说(具体可以参考PEP8),你应该使用isinstance
来判断类型,而不是直接比较类型:
if isinstance(dict_foo[field], dict)
不过,如果dict_foo[field]
是dict
的子类,这样的判断也会返回True
。如果你不想要这种情况,可以用is
来代替==
。这样会稍微快一点(可能你不会注意到)。
如果你还想允许任意类似字典的对象,可以进一步测试它是否是collections.abc.MutableMapping
。这样对于dict
及其子类,以及所有明确实现了该接口的可变映射(而不是继承自dict
)都会返回True
,比如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.append
、dict.clear
、dict.update
都是就地修改数据结构,并且return None
。这样可以更容易地跟踪函数的作用。不过这并不是硬性规定,总是有例外存在。但我个人认为像这样的函数不需要例外,我会直接去掉return dict_del
这一行,让它隐式返回None
,但这只是个人看法。
从字典中移除键
你复制了字典,以避免在迭代过程中移除键值对时出现问题。不过,正如其他回答提到的,你可以直接迭代要删除的键,然后尝试删除它们:
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
听起来像是一个被删除的字典。我更倾向于使用dictionary
或dct
这样的名字,因为函数名已经描述了对字典所做的操作。lst_keys
也是如此。我可能会直接用keys
。如果想更具体一些,可以用keys_sequence
,因为它接受任何sequence
(只要能多次迭代),而不仅仅是列表。dict_foo
,这就不行……field
也不太合适,它应该是一个键。
总结一下:
如我之前所说,我个人会选择就地修改字典,而不再返回字典。因此,我提供了两个解决方案,一个是就地修改但不返回任何东西,另一个是创建一个新的字典,移除指定的键。
就地修改的版本(非常类似于Ned Batchelder的解决方案):
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
来包装这些(我在代码的适当位置加了注释)。