Рекурсивный поиск Dict на Python с вложенными ключами
недавно мне пришлось решить проблему в реальной системе данных с вложенной комбинацией dict/list. Я работал над этим довольно долго и придумал решение, но я очень недоволен. Мне пришлось прибегнуть к помощи globals()
и именованный временный глобальный параметр.
Я не люблю использовать глобалы. Это всего лишь попытка сделать инъекцию. Я чувствую, что должен быть лучший способ выполнить эту задачу, не прибегая к глобалам.
проблема Набор данных:
d = {
"k":1,
"stuff":"s1",
"l":{"m":[
{
"k":2,
"stuff":"s2",
"l":None
},
{
"k":3,
"stuff":"s3",
"l":{"m":[
{
"k":4,
"stuff":"s4",
"l":None
},
{
"k":5,
"stuff":"s5",
"l":{"m":[
{
"k":6,
"stuff":"s6",
"l":None
},
]}
},
]}
},
]}
}
Желаемый Результат:
[{'k': 1, 'stuff': 's1'},
{'k': 2, 'stuff': 's2'},
{'k': 3, 'stuff': 's3'},
{'k': 4, 'stuff': 's4'},
{'k': 5, 'stuff': 's5'},
{'k': 6, 'stuff': 's6'}]
Мое Решение:
def _get_recursive_results(d, iter_key, get_keys):
if not 'h' in globals():
global h
h = []
h.append({k:d.get(k) for k in get_keys})
d2 = d.copy()
for k in iter_key:
if not d2:
continue
d2 = d2.get(k)
for td in d2:
d3 = td.copy()
for k in iter_key:
if not d3:
continue
d3 = d3.get(k)
if d3:
return _get_recursive_results(td, iter_key, get_keys)
h.append({k:td.get(k) for k in get_keys})
else:
l = [k for k in h]
del globals()['h']
return l
вызов моей функции следующим образом возвращает желаемый результат:
_get_recursively(d, ['l','m'], ['k','stuff'])
как бы я построил лучшее решение?
6 ответов
это слегка измененная версия без использования глобальных переменных. Set h
to None
по умолчанию и создать новый список для первого вызова _get_recursive_results()
. Позже предоставить h
в качестве аргумента в рекурсивных вызовов _get_recursive_results()
:
def _get_recursive_results(d, iter_key, get_keys, h=None):
if h is None:
h = []
h.append({k:d.get(k) for k in get_keys})
d2 = d.copy()
for k in iter_key:
if not d2:
continue
d2 = d2.get(k)
for td in d2:
d3 = td.copy()
for k in iter_key:
if not d3:
continue
d3 = d3.get(k)
if d3:
return _get_recursive_results(td, iter_key, get_keys, h)
h.append({k:td.get(k) for k in get_keys})
else:
l = [k for k in h]
return l
теперь:
>>> _get_recursive_results(d, ['l','m'], ['k','stuff'])
[{'k': 1, 'stuff': 's1'},
{'k': 2, 'stuff': 's2'},
{'k': 3, 'stuff': 's3'},
{'k': 4, 'stuff': 's4'},
{'k': 5, 'stuff': 's5'},
{'k': 6, 'stuff': 's6'}]
нет необходимости в копировании промежуточных диктов. Это еще одна модифицированная версия без копирования:
def _get_recursive_results(d, iter_key, get_keys, h=None):
if h is None:
h = []
h.append({k: d.get(k) for k in get_keys})
for k in iter_key:
if not d:
continue
d = d.get(k)
for td in d:
d3 = td
for k in iter_key:
if not d3:
continue
d3 = d3.get(k)
if d3:
return _get_recursive_results(td, iter_key, get_keys, h)
h.append({k: td.get(k) for k in get_keys})
else:
return h
Это не так универсально, но это делает работу:
def parse_tree(d, keys):
result = [{key: d[key] for key in keys}]
l = d.get('l', None)
if l is not None:
entries = l.get('m', [])
for entry in entries:
result.extend(parse_tree(entry))
return result
>>> parse_tree(d, ['k', 'stuff'])
[{'k': 1, 'stuff': 's1'},
{'k': 2, 'stuff': 's2'},
{'k': 3, 'stuff': 's3'},
{'k': 4, 'stuff': 's4'},
{'k': 5, 'stuff': 's5'},
{'k': 6, 'stuff': 's6'}]
использовать генератор
при следующих генераторов:
def get_stuff(dct, iter_keys, get_keys):
k, stuff = get_keys
l, m = iter_keys
if k in dct:
yield {k: dct[k], stuff: dct[stuff]}
if dct.get(l):
for subdct in dct[l][m]:
for res in get_stuff(subdct, iter_keys, get_keys):
yield res
list(get_stuff(d, ["l", "m"], ["k", "stuff"]))
вы получили результаты на:
list(get_stuff(d))
Python 3.3 предоставляет новые yield from
выражение, используемое для делегирования уступки подгенератору. Используя это выражение, код может быть на одну строку короче:
def get_stuff(dct):
if "k" in dct:
yield {"k": dct["k"], "stuff": dct["stuff"]}
if dct.get("l"):
for subdct in dct["l"]["m"]:
yield from get_stuff(subdct)
def get_stuff(dct, iter_keys, get_keys):
k, stuff = get_keys
l, m = iter_keys
if k in dct:
yield {k: dct[k], stuff: dct[stuff]}
if dct.get(l):
for subdct in dct[l][m]:
yield from get_stuff(subdct, iter_keys, get_keys):
некоторые методы, чтобы избежать globals
генераторы
часто, если вам нужно создать список и искать замену глобальных переменных, генераторы можно пригодится, поскольку они сохраняют статус текущей работы в своих локальных переменных плюс построение всего результата откладывается на потребление сгенерированных значений.
рекурсия
рекурсия хранить subresults в локальных переменных в стеке.
экземпляр класса с внутренним собственность
класс может служить tin для инкапсуляции ваших переменных.
вместо использования глобальной переменной, вы храните промежуточный результат в экземпляре свойство.
обобщить для различных структур данных
в ваших комментариях вы упомянули, что вы получаете много разных типов с каждым дампом.
я буду считать, что ваши данные соответствуют следующим ожиданиям:
- имеет древовидную структуру
- каждый узел в дереве должен внести свой вклад в результат некоторого результата (например, словарь
{"k": xx, "stuff": yy}
) - каждый узел может содержать подэлементы (список подузлы)
один из вариантов сделать решение более общим-предоставить список ключей для использования чтобы получить доступ к значению / подэлементам, другой вариант-предоставить функцию, которая выполняет работу по получению значения узла и подэлементов.
здесь я использую get_value
для доставки значения узла и get_subitems
для доставки подузлов:
def get_value(data):
try:
return {"k": data["k"], "stuff": data["stuff"]}
except KeyError:
return None
def get_subitems(data):
try:
return data["l"]["m"]
except TypeError:
return None
обработка затем выполняется:
def get_stuff(dct, get_value_fun, get_subitems_fun):
value = get_value(dct)
if value:
yield value
lst = get_subitems_fun(dct)
if lst:
for subdct in lst:
for res in get_stuff(subdct, get_value_fun, get_subitems_fun):
yield res
называют таким образом:
get_stuff(d, get_value, get_subitems)
преимущество использование функций заключается в том, что он гораздо более гибок для любых данных
структуры, которые вам придется обрабатывать (адаптация к другим структурам данных потребует только предоставления настроенной версии функции get_value
и get_subitems
- с одинаковыми или разными именами в соответствии с вашими предпочтениями.
Edit: в первой версии была ошибка, которая теперь исправлена
Я считаю, что это должно работать, мы используем силу рекурсия!
def strip_leaves_from_tree(my_tree):
result = list()
row = dict()
for key in my_tree:
child = my_tree[key]
if type(child) in (int, str,):
row[key] = child
elif isinstance(child, dict):
result = strip_leaves_from_tree(child)
elif isinstance(child, list):
for element in child:
result += strip_leaves_from_tree(element)
if row: result = [row,]+result
return result
Я проверил, что он работает. Пожалуйста, проверьте. Конечно, он должен быть изменен при изменении структуры словаря-список.
def add(ret, val):
if val is not None: ret.append(val)
def flatten(d, ret):
for k,v in d.items():
if isinstance(v, dict): add(ret,flatten(v, ret))
elif isinstance(v, list):
for i in v: add(ret, flatten(i, ret))
elif k=='k':
ret.append({'k':v,'stuff':d.get('stuff')})
ret = []
flatten(d, ret)
взгляните на https://github.com/akesterson/dpath-python/blob/master/README.rst
Это хороший способ поиска по дикт