在字典中递归查找键
Posted
技术标签:
【中文标题】在字典中递归查找键【英文标题】:Finding a key recursively in a dictionary 【发布时间】:2013-02-04 10:18:01 【问题描述】:我正在尝试编写一个非常简单的函数来递归搜索可能嵌套的(在最极端的情况下十层深)Python 字典并返回它从给定键中找到的第一个值。
我不明白为什么我的代码不适用于嵌套字典。
def _finditem(obj, key):
if key in obj: return obj[key]
for k, v in obj.items():
if isinstance(v,dict):
_finditem(v, key)
print _finditem("B":"A":2,"A")
它返回None
。
但是,对于_finditem("B":1,"A":2,"A")
,它确实有效,返回2
。
我确定这是一个简单的错误,但我找不到它。我觉得标准库或collections
中可能已经有相关内容,但我也找不到。
【问题讨论】:
请注意,检查它是否是dict
对象是个坏主意,因为它排除了类似 dict
的对象。相反,请执行 try: ...
except TypeError: ...
。 (请求宽恕,而不是许可)。
还要注意,由于字典本质上是无序的,如果你的嵌套结构中有多个键“A”,你永远不知道你会得到哪一个(就像一盒巧克力我想。 ..)
@mgilson 在这个特定的情况下,没关系,我考虑过。 :)
@frb -- 我认为它可能没问题,我只是想确保它被记录在某个地方:)。
【参考方案1】:
递归的时候需要return
_finditem
的结果
def _finditem(obj, key):
if key in obj: return obj[key]
for k, v in obj.items():
if isinstance(v,dict):
return _finditem(v, key) #added return statement
要修复实际算法,您需要意识到 _finditem
如果没有找到任何东西,则返回 None
,因此您需要明确检查以防止提前返回:
def _finditem(obj, key):
if key in obj: return obj[key]
for k, v in obj.items():
if isinstance(v,dict):
item = _finditem(v, key)
if item is not None:
return item
当然,如果您的任何字典中有 None
值,这将失败。在这种情况下,你可以为这个函数设置一个标记object()
,并在你没有找到任何东西的情况下返回它——然后你可以检查sentinel
以了解你是否找到了一些东西。
【讨论】:
这似乎是编写递归函数时最常见的错误。 @DanielRoseman -- 耸耸肩 -- 我自己也犯过几次这个错误。但是,当您的函数返回None
而您不知道为什么时,这是一个提示;-)
谢谢,这应该是显而易见的。我看了这个好一个小时!
@frb -- 没问题。这样的事情发生在每个人身上。
@mgilson 实际上,我刚刚意识到,当需要的键在字典中是第二个时,这不起作用。有什么线索吗?这是因为它在 obj.items() 中的第一个键值之后返回。【参考方案2】:
这是一个搜索包含嵌套字典和列表的字典的函数。它会创建一个结果值列表。
def get_recursively(search_dict, field):
"""
Takes a dict with nested lists and dicts,
and searches all dicts for a key of the field
provided.
"""
fields_found = []
for key, value in search_dict.iteritems():
if key == field:
fields_found.append(value)
elif isinstance(value, dict):
results = get_recursively(value, field)
for result in results:
fields_found.append(result)
elif isinstance(value, list):
for item in value:
if isinstance(item, dict):
more_results = get_recursively(item, field)
for another_result in more_results:
fields_found.append(another_result)
return fields_found
【讨论】:
用 items() 替换 iteritems() 以使其在 python3 中工作:docs.buildbot.net/0.9.4/developer/py3-compat.html 使用 isinstance(value, (list, tuple)) 它也会在嵌套元组中搜索【参考方案3】:这是一种使用“堆栈”和"stack of iterators" pattern(感谢 Gareth Rees)的方法:
def search(d, key, default=None):
"""Return a value corresponding to the specified key in the (possibly
nested) dictionary d. If there is no item with that key, return
default.
"""
stack = [iter(d.items())]
while stack:
for k, v in stack[-1]:
if isinstance(v, dict):
stack.append(iter(v.items()))
break
elif k == key:
return v
else:
stack.pop()
return default
print(search("B": "A": 2, "A"))
将打印 2
。
【讨论】:
【参考方案4】:只是想让它更短:
def get_recursively(search_dict, field):
if isinstance(search_dict, dict):
if field in search_dict:
return search_dict[field]
for key in search_dict:
item = get_recursively(search_dict[key], field)
if item is not None:
return item
elif isinstance(search_dict, list):
for element in search_dict:
item = get_recursively(element, field)
if item is not None:
return item
return None
【讨论】:
【参考方案5】:由于缺乏声誉,我无法对@mgilston 提出的已接受解决方案添加评论。如果要搜索的键在列表中,则该解决方案不起作用。
遍历列表的元素并调用递归函数应该扩展功能以在嵌套列表中查找元素:
def _finditem(obj, key):
if key in obj: return obj[key]
for k, v in obj.items():
if isinstance(v,dict):
item = _finditem(v, key)
if item is not None:
return item
elif isinstance(v,list):
for list_item in v:
item = _finditem(list_item, key)
if item is not None:
return item
print(_finditem("C": "B": ["A":2], "A"))
【讨论】:
【参考方案6】:我必须创建一个通用版本,它在包含多个嵌套字典和列表的字典中找到唯一指定的键(指定所需值的路径的最小字典)。
对于下面的示例,创建了一个目标字典进行搜索,并使用通配符“???”创建了键。运行时返回值“D”
def lfind(query_list:List, target_list:List, targ_str:str = "???"):
for tval in target_list:
#print("lfind: tval = , query_list[0] = ".format(tval, query_list[0]))
if isinstance(tval, dict):
val = dfind(query_list[0], tval, targ_str)
if val:
return val
elif tval == query_list[0]:
return tval
def dfind(query_dict:Dict, target_dict:Dict, targ_str:str = "???"):
for key, qval in query_dict.items():
tval = target_dict[key]
#print("dfind: key = , qval = , tval = ".format(key, qval, tval))
if isinstance(qval, dict):
val = dfind(qval, tval, targ_str)
if val:
return val
elif isinstance(qval, list):
return lfind(qval, tval, targ_str)
else:
if qval == targ_str:
return tval
if qval != tval:
break
def find(target_dict:Dict, query_dict:Dict):
result = dfind(query_dict, target_dict)
return result
target_dict = "A":[
"key1":"A", "key2":"key3": "B",
"key1":"C", "key2":"key3": "D"]
query_dict = "A":["key1":"C", "key2":"key3": "???"]
result = find(target_dict, query_dict)
print("result = ".format(result))
【讨论】:
【参考方案7】:我想我会把我的帽子扔在戒指上,这将允许对任何实现 __getitem__
方法的递归请求。
def _get_recursive(obj, args, default=None):
"""Apply successive requests to an obj that implements __getitem__ and
return result if something is found, else return default"""
if not args:
return obj
try:
key, *args = args
_obj = object.__getitem__(obj, key)
return _get_recursive(_obj, args, default=default)
except (KeyError, IndexError, AttributeError):
return default
【讨论】:
以上是关于在字典中递归查找键的主要内容,如果未能解决你的问题,请参考以下文章