У меня есть такой словарь:
{ "id" : "abcde",
"key1" : "blah",
"key2" : "blah blah",
"nestedlist" : [
{ "id" : "qwerty",
"nestednestedlist" : [
{ "id" : "xyz",
"keyA" : "blah blah blah" },
{ "id" : "fghi",
"keyZ" : "blah blah blah" }],
"anothernestednestedlist" : [
{ "id" : "asdf",
"keyQ" : "blah blah" },
{ "id" : "yuiop",
"keyW" : "blah" }] } ] }
В основном это словарь с вложенными списками, словарями и строками произвольной глубины.
Как лучше всего обойти это, чтобы извлечь значения каждого ключа "id"? Я хочу получить эквивалент запроса XPath, например «// id». Значение «id» всегда является строкой.
Итак, в моем примере результат, который мне нужен, в основном:
["abcde", "qwerty", "xyz", "fghi", "asdf", "yuiop"]
Порядок не важен.
python
recursion
dictionary
traversal
Мэтт Суэйн
источник
источник
None
на вход. Вы заботитесь о надежности? (поскольку теперь это используется как канонический вопрос)Ответы:
Я нашел этот вопрос / ответ очень интересным, поскольку он предлагает несколько различных решений одной и той же проблемы. Я взял все эти функции и протестировал их со сложным словарным объектом. Мне пришлось исключить две функции из теста, потому что у них было много неудачных результатов, и они не поддерживали возвращение списков или диктов в качестве значений, что я считаю важным, поскольку функция должна быть подготовлена практически для любых данных.
Итак, я прокачал другие функции за 100000 итераций через
timeit
модуль, и на выходе получился следующий результат:0.11 usec/pass on gen_dict_extract(k,o) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 6.03 usec/pass on find_all_items(k,o) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0.15 usec/pass on findkeys(k,o) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1.79 usec/pass on get_recursively(k,o) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0.14 usec/pass on find(k,o) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0.36 usec/pass on dict_extract(k,o) - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
У всех функций была одна и та же стрелка для поиска ('ведение журнала') и один и тот же объект словаря, который построен следующим образом:
o = { 'temparature': '50', 'logging': { 'handlers': { 'console': { 'formatter': 'simple', 'class': 'logging.StreamHandler', 'stream': 'ext://sys.stdout', 'level': 'DEBUG' } }, 'loggers': { 'simpleExample': { 'handlers': ['console'], 'propagate': 'no', 'level': 'INFO' }, 'root': { 'handlers': ['console'], 'level': 'DEBUG' } }, 'version': '1', 'formatters': { 'simple': { 'datefmt': "'%Y-%m-%d %H:%M:%S'", 'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s' } } }, 'treatment': {'second': 5, 'last': 4, 'first': 4}, 'treatment_plan': [[4, 5, 4], [4, 5, 4], [5, 5, 5]] }
Все функции дали одинаковый результат, но разница во времени огромна! Функция
gen_dict_extract(k,o)
- это моя функция, адаптированная из приведенных здесь функций, на самом деле она очень похожа наfind
функцию из Alfe, с основным отличием, что я проверяю, имеет ли данный объект функцию iteritems, в случае, если строки передаются во время рекурсии:def gen_dict_extract(key, var): if hasattr(var,'iteritems'): for k, v in var.iteritems(): if k == key: yield v if isinstance(v, dict): for result in gen_dict_extract(key, v): yield result elif isinstance(v, list): for d in v: for result in gen_dict_extract(key, d): yield result
Таким образом, этот вариант является самым быстрым и безопасным из имеющихся здесь функций. И
find_all_items
он невероятно медленный и далеко от второго по скорости, вget_recursivley
то время как остальные, за исключениемdict_extract
, близки друг к другу. Функцииfun
иkeyHole
работают только в том случае, если вы ищете строки.Интересный аспект обучения :)
источник
gen_dict_extract(keys, var)
(2) поместитеfor key in keys:
как строку 2 и сделайте отступ для остальных (3) измените первый выход наyield {key: v}
next(functionname(k, o)
для всех решений для генераторов.hasattr(var, 'items')
для python3if hasattr
части для версии, использующейtry
для перехвата исключения в случае сбоя вызова (см. Pastebin.com/ZXvVtV0g для возможной реализации)? Это уменьшит двойной поиск атрибутаiteritems
(один раз дляhasattr()
и один раз для вызова) и, таким образом, вероятно, сократит время выполнения (что кажется вам важным). Но никаких тестов не проводил.iteritems
это сталоitems
.d = { "id" : "abcde", "key1" : "blah", "key2" : "blah blah", "nestedlist" : [ { "id" : "qwerty", "nestednestedlist" : [ { "id" : "xyz", "keyA" : "blah blah blah" }, { "id" : "fghi", "keyZ" : "blah blah blah" }], "anothernestednestedlist" : [ { "id" : "asdf", "keyQ" : "blah blah" }, { "id" : "yuiop", "keyW" : "blah" }] } ] } def fun(d): if 'id' in d: yield d['id'] for k in d: if isinstance(d[k], list): for i in d[k]: for j in fun(i): yield j
>>> list(fun(d)) ['abcde', 'qwerty', 'xyz', 'fghi', 'asdf', 'yuiop']
источник
for k in d
наfor k,value in d.items()
с последующим использованиемvalue
вместо вместоd[k]
.gen_dict_extract
d = { "id" : "abcde", "key1" : "blah", "key2" : "blah blah", "nestedlist" : [ { "id" : "qwerty", "nestednestedlist" : [ { "id" : "xyz", "keyA" : "blah blah blah" }, { "id" : "fghi", "keyZ" : "blah blah blah" }], "anothernestednestedlist" : [ { "id" : "asdf", "keyQ" : "blah blah" }, { "id" : "yuiop", "keyW" : "blah" }] } ] } def findkeys(node, kv): if isinstance(node, list): for i in node: for x in findkeys(i, kv): yield x elif isinstance(node, dict): if kv in node: yield node[kv] for j in node.values(): for x in findkeys(j, kv): yield x print(list(findkeys(d, 'id')))
источник
def find(key, value): for k, v in value.iteritems(): if k == key: yield v elif isinstance(v, dict): for result in find(key, v): yield result elif isinstance(v, list): for d in v: for result in find(key, d): yield result
РЕДАКТИРОВАТЬ: @Anthon заметил, что это не сработает для напрямую вложенных списков. Если у вас есть это на входе, вы можете использовать это:
def find(key, value): for k, v in (value.iteritems() if isinstance(value, dict) else enumerate(value) if isinstance(value, list) else []): if k == key: yield v elif isinstance(v, (dict, list)): for result in find(key, v): yield result
Но я думаю, что исходную версию легче понять, поэтому я оставлю ее.
источник
isinstance
проверкиdict
перед двумя последними строками решит эту проблему.Другой вариант, который включает вложенный путь к найденным результатам ( примечание: в этой версии списки не рассматриваются ):
def find_all_items(obj, key, keys=None): """ Example of use: d = {'a': 1, 'b': 2, 'c': {'a': 3, 'd': 4, 'e': {'a': 9, 'b': 3}, 'j': {'c': 4}}} for k, v in find_all_items(d, 'a'): print "* {} = {} *".format('->'.join(k), v) """ ret = [] if not keys: keys = [] if key in obj: out_keys = keys + [key] ret.append((out_keys, obj[key])) for k, v in obj.items(): if isinstance(v, dict): found_items = find_all_items(v, key, keys=(keys+[k])) ret += found_items return ret
источник
Я просто хотел повторить отличный ответ @hexerei-software, используя
yield from
и принимая списки верхнего уровня.def gen_dict_extract(var, key): if isinstance(var, dict): for k, v in var.items(): if k == key: yield v if isinstance(v, (dict, list)): yield from gen_dict_extract(v, key) elif isinstance(var, list): for d in var: yield from gen_dict_extract(d, key)
источник
for key in keys
. Кроме того, я добавил к 2isinstance
в(list, tuple)
течение еще дополнительной разновидности. ;)Эта функция рекурсивно ищет словарь, содержащий вложенные словари и списки. Он создает список под названием fields_found, который содержит значение для каждого случая обнаружения поля. «Поле» - это ключ, который я ищу в словаре, его вложенных списках и словарях.
источник
Вот мой удар:
def keyHole(k2b,o): # print "Checking for %s in "%k2b,o if isinstance(o, dict): for k, v in o.iteritems(): if k == k2b and not hasattr(v, '__iter__'): yield v else: for r in keyHole(k2b,v): yield r elif hasattr(o, '__iter__'): for r in [ keyHole(k2b,i) for i in o ]: for r2 in r: yield r2 return
Пример:
>>> findMe = {'Me':{'a':2,'Me':'bop'},'z':{'Me':4}} >>> keyHole('Me',findMe) <generator object keyHole at 0x105eccb90> >>> [ x for x in keyHole('Me',findMe) ] ['bop', 4]
источник
Следуя ответу программного обеспечения @hexerei и комментарию @bruno-bronosky, если вы хотите перебрать список / набор ключей:
def gen_dict_extract(var, keys): for key in keys: if hasattr(var, 'items'): for k, v in var.items(): if k == key: yield v if isinstance(v, dict): for result in gen_dict_extract([key], v): yield result elif isinstance(v, list): for d in v: for result in gen_dict_extract([key], d): yield result
Обратите внимание, что я передаю список с одним элементом ([key]} вместо строкового ключа.
источник