Перебрать все значения вложенного словаря?

121
for k, v in d.iteritems():
    if type(v) is dict:
        for t, c in v.iteritems():
            print "{0} : {1}".format(t, c)

Я пытаюсь перебрать словарь и распечатать все пары «ключ-значение», где значение не является вложенным словарем. Если значение является словарем, я хочу войти в него и распечатать его пары ключ-значение ... и т. Д. Любая помощь?

РЕДАКТИРОВАТЬ

Как насчет этого? Он по-прежнему печатает только одно.

def printDict(d):
    for k, v in d.iteritems():
        if type(v) is dict:
            printDict(v)
        else:
            print "{0} : {1}".format(k, v)

Полный тестовый пример

Словарь:

{u'xml': {u'config': {u'portstatus': {u'status': u'good'}, u'target': u'1'},
      u'port': u'11'}}

Результат:

xml : {u'config': {u'portstatus': {u'status': u'good'}, u'target': u'1'}, u'port': u'11'}
Takkun
источник
1
Похоже, вам нужна рекурсия, но описание недостаточно четкое, чтобы быть уверенным. А как насчет примера ввода / вывода? Кроме того, что не так с вашим кодом?
Никлас Б.
2
В Python существует фиксированный предел рекурсии: docs.python.org/library/sys.html#sys.setrecursionlimit
доктор Ян-Филип Герке,
2
@ Jan-PhilipGehrcke: Реализация алгоритмов на древовидной структуре данных без рекурсии - это чистое самоубийство.
Никлас Б.
2
@Takkun: Вы используете dictв качестве имени переменной. Никогда не делайте этого (вот почему это не удается).
Никлас Б.
3
@NiklasB., Re: "suicide": я только что реализовал итеративную версию алгоритма Шаррона, длина которой всего на две строки длиннее, и ее все еще довольно легко понять. Кроме того, преобразование рекурсии в итерацию часто требуется при переходе от деревьев к общим графам.
Фред Фу

Ответы:

158

Как сказал Никлас, вам нужна рекурсия, то есть вы хотите определить функцию для печати вашего dict, и если значение является dict, вы хотите вызвать свою функцию печати, используя этот новый dict.

Что-то вроде :

def myprint(d):
    for k, v in d.items():
        if isinstance(v, dict):
            myprint(v)
        else:
            print("{0} : {1}".format(k, v))
Scharron
источник
3
небольшое улучшение. добавьте print (k) перед вызовом myprint (v).
Наоми Фридман
С рекурсией все тривиально.
sergzach
37

Если вы напишете собственную рекурсивную реализацию или итерационный эквивалент со стеком , возникнут потенциальные проблемы . См. Этот пример:

    dic = {}
    dic["key1"] = {}
    dic["key1"]["key1.1"] = "value1"
    dic["key2"]  = {}
    dic["key2"]["key2.1"] = "value2"
    dic["key2"]["key2.2"] = dic["key1"]
    dic["key2"]["key2.3"] = dic

В обычном смысле вложенный словарь представляет собой структуру данных, подобную n-нному дереву. Но определение не исключает возможности перекрестного ребра или даже заднего ребра (таким образом, это уже не дерево). Например, здесь key2.2 содержит словарь из key1 , key2.3 указывает на весь словарь (задний край / цикл). Когда есть задний край (цикл), стек / рекурсия будет работать бесконечно.

                          root<-------back edge
                        /      \           |
                     _key1   __key2__      |
                    /       /   \    \     |
               |->key1.1 key2.1 key2.2 key2.3
               |   /       |      |
               | value1  value2   |
               |                  | 
              cross edge----------|

Если вы распечатаете этот словарь с помощью этой реализации от Scharron

    def myprint(d):
      for k, v in d.items():
        if isinstance(v, dict):
          myprint(v)
        else:
          print "{0} : {1}".format(k, v)

Вы увидите эту ошибку:

    RuntimeError: maximum recursion depth exceeded while calling a Python object

То же самое и с реализацией от senderle .

Точно так же вы получаете бесконечный цикл с этой реализацией от Фреда Фу :

    def myprint(d):
        stack = list(d.items())
        while stack:
            k, v = stack.pop()
            if isinstance(v, dict):
                stack.extend(v.items())
            else:
                print("%s: %s" % (k, v))

Однако Python фактически обнаруживает циклы во вложенном словаре:

    print dic
    {'key2': {'key2.1': 'value2', 'key2.3': {...}, 
       'key2.2': {'key1.1': 'value1'}}, 'key1': {'key1.1': 'value1'}}

«{...}» - это место, где обнаружен цикл.

По просьбе Moondra это способ избежать циклов (DFS):

def myprint(d): 
  stack = list(d.items()) 
  visited = set() 
  while stack: 
    k, v = stack.pop() 
    if isinstance(v, dict): 
      if k not in visited: 
        stack.extend(v.items()) 
      else: 
        print("%s: %s" % (k, v)) 
      visited.add(k)
тенгр
источник
Итак, как бы вы реализовали итеративное решение?
dreftymac
2
@dreftymac Я бы добавил посещенный набор ключей, чтобы избежать циклов:def myprint(d): stack = d.items() visited = set() while stack: k, v = stack.pop() if isinstance(v, dict): if k not in visited: stack.extend(v.iteritems()) else: print("%s: %s" % (k, v)) visited.add(k)
tengr
1
Спасибо за указание на это. Не могли бы вы включить свой код в ответ. Думаю, это завершает ваш отличный ответ.
Moondra
Для Python3 используйте list(d.items())as, d.items()возвращая представление, а не список, и используйте v.items()вместоv.iteritems()
Max
33

Поскольку a dictявляется итерируемым, вы можете применить к этой проблеме классическую итеративную формулу вложенного контейнера, сделав лишь пару незначительных изменений. Вот версия Python 2 (см. 3 ниже):

import collections
def nested_dict_iter(nested):
    for key, value in nested.iteritems():
        if isinstance(value, collections.Mapping):
            for inner_key, inner_value in nested_dict_iter(value):
                yield inner_key, inner_value
        else:
            yield key, value

Тест:

list(nested_dict_iter({'a':{'b':{'c':1, 'd':2}, 
                            'e':{'f':3, 'g':4}}, 
                       'h':{'i':5, 'j':6}}))
# output: [('g', 4), ('f', 3), ('c', 1), ('d', 2), ('i', 5), ('j', 6)]

В Python 2 можно было бы создать кастом, Mappingкоторый квалифицируется как, Mappingно не содержит iteritems, и в этом случае это не удастся. В документации не указано, что iteritemsтребуется для Mapping: с другой стороны, источник предоставляет Mappingтипам iteritemsметод. Так что для обычаев на всякий случай Mappingsнаследуйте от collections.Mappingявно.

В Python 3 необходимо внести ряд улучшений. Начиная с Python 3.3, абстрактные базовые классы живут в collections.abc. Они collectionsтоже остаются для обратной совместимости, но лучше, если наши абстрактные базовые классы объединены в одном пространстве имен. Итак, это импорт abcиз collections. Также добавляет Python 3.3 yield from, который разработан как раз для такого рода ситуаций. Это не пустой синтаксический сахар; это может привести к более быстрому коду и более разумному взаимодействию с сопрограммами .

from collections import abc
def nested_dict_iter(nested):
    for key, value in nested.items():
        if isinstance(value, abc.Mapping):
            yield from nested_dict_iter(value)
        else:
            yield key, value
senderle
источник
3
isinstance(item, collections.Iterable)нет гарантии hasattr(item, "iteritems"). Проверять collections.Mappingлучше.
Фред Фу
1
@larsmans, вы, конечно, совершенно правы. Я думал, что использование Iterableсделает это решение более универсальным, забыв о том, что, очевидно, итерации не обязательно должны быть iteritems.
senderle
+1 к этому ответу, потому что это общее решение, которое работает для этой проблемы, но оно не ограничивается простой печатью значений. @Takkun, вам обязательно стоит рассмотреть этот вариант. В конечном итоге вам понадобится нечто большее, чем просто распечатать значения.
Алехандро Пиад
1
@ Seanny123, Спасибо, что обратил на это мое внимание. На самом деле Python 3 меняет картину несколькими способами - я собираюсь переписать это как версию, использующую новый yield fromсинтаксис.
senderle
25

Альтернативное итеративное решение:

def myprint(d):
    stack = d.items()
    while stack:
        k, v = stack.pop()
        if isinstance(v, dict):
            stack.extend(v.iteritems())
        else:
            print("%s: %s" % (k, v))
Фред Фу
источник
2
Да, вот как я себе это представлял. Спасибо. Таким образом, преимущество этого метода в том, что он не переполняет стек из-за очень глубоких вложений? Или в этом есть что-то еще?
Никлас Б.
@NiklasB .: да, это первое преимущество. Кроме того, эту версию можно довольно легко адаптировать к различным порядкам обхода, заменив стек (a list) dequeочередью или даже очередью с приоритетом.
Фред Фу
Да, имеет смысл. Спасибо и удачного кодирования :)
Никлас Б.
Да, но это решение занимает больше места, чем мое и рекурсивное.
schlamar
1
@ ms4py: Ради интереса я создал тест . На моем компьютере рекурсивная версия является самой быстрой, а larsmans занимает второе место среди всех трех тестовых словарей. Версия, использующая генераторы, относительно медленная, как и ожидалось (потому что в ней приходится много жонглировать различными контекстами генератора)
Никлас Б.
10

Немного другая версия, которую я написал, отслеживает ключи по пути, чтобы добраться туда

def print_dict(v, prefix=''):
    if isinstance(v, dict):
        for k, v2 in v.items():
            p2 = "{}['{}']".format(prefix, k)
            print_dict(v2, p2)
    elif isinstance(v, list):
        for i, v2 in enumerate(v):
            p2 = "{}[{}]".format(prefix, i)
            print_dict(v2, p2)
    else:
        print('{} = {}'.format(prefix, repr(v)))

На ваших данных он напечатает

data['xml']['config']['portstatus']['status'] = u'good'
data['xml']['config']['target'] = u'1'
data['xml']['port'] = u'11'

Его также легко изменить, чтобы отслеживать префикс как кортеж ключей, а не строку, если он вам так нужен.

Эхсан Киа
источник
Как добавить результат в список?
Shash
5

Вот питонический способ сделать это. Эта функция позволит вам перебирать пару ключ-значение на всех уровнях. Он не сохраняет все это в памяти, а скорее проходит через dict, когда вы его просматриваете

def recursive_items(dictionary):
    for key, value in dictionary.items():
        if type(value) is dict:
            yield (key, value)
            yield from recursive_items(value)
        else:
            yield (key, value)

a = {'a': {1: {1: 2, 3: 4}, 2: {5: 6}}}

for key, value in recursive_items(a):
    print(key, value)

Печать

a {1: {1: 2, 3: 4}, 2: {5: 6}}
1 {1: 2, 3: 4}
1 2
3 4
2 {5: 6}
5 6
Дмитрий Торба
источник
3

Альтернативное решение для работы со списками на основе решения Шаррона

def myprint(d):
    my_list = d.iteritems() if isinstance(d, dict) else enumerate(d)

    for k, v in my_list:
        if isinstance(v, dict) or isinstance(v, list):
            myprint(v)
        else:
            print u"{0} : {1}".format(k, v)
Габриэль
источник
2

Итерационное решение как альтернатива:

def traverse_nested_dict(d):
    iters = [d.iteritems()]

    while iters:
        it = iters.pop()
        try:
            k, v = it.next()
        except StopIteration:
            continue

        iters.append(it)

        if isinstance(v, dict):
            iters.append(v.iteritems())
        else:
            yield k, v


d = {"a": 1, "b": 2, "c": {"d": 3, "e": {"f": 4}}}
for k, v in traverse_nested_dict(d):
    print k, v
Schlamar
источник
Как так? Большой O должен быть таким же (это O(depth)для рекурсивного решения. То же относится и к этой версии, если я правильно думаю).
Никлас Б.
«Скопировать стопку»? О чем ты говоришь? Каждый вызов функции создает новый стековый фрейм. В вашем решении используется itersявный стек, поэтому потребление памяти Big-O такое же, или мне что-то не хватает?
Никлас Б.
@NiklasB. Рекурсия всегда сопряжена с накладными расходами, подробности см. В этом разделе Википедии: en.wikipedia.org/wiki/… Фрейм стека рекурсивного решения намного больше.
schlamar
Вы, должно быть, неправильно поняли этот абзац. Это ничего не говорит в поддержку ваших заявлений.
Никлас Б.
1
@NiklasB. Нет, потому что фрейм стека здесь - это только iter, а для рекурсивного решения у фрейма стека есть iter, программный счетчик, переменная среда и т. Д.
schlamar
2

Я использую следующий код для печати всех значений вложенного словаря с учетом того, где значение может быть списком, содержащим словари. Это было полезно для меня при разборе файла JSON в словарь и необходимости быстро проверить, соответствуют ли какие-либо из его значений None.

    d = {
            "user": 10,
            "time": "2017-03-15T14:02:49.301000",
            "metadata": [
                {"foo": "bar"},
                "some_string"
            ]
        }


    def print_nested(d):
        if isinstance(d, dict):
            for k, v in d.items():
                print_nested(v)
        elif hasattr(d, '__iter__') and not isinstance(d, str):
            for item in d:
                print_nested(item)
        elif isinstance(d, str):
            print(d)

        else:
            print(d)

    print_nested(d)

Вывод:

    10
    2017-03-15T14:02:49.301000
    bar
    some_string
сигма
источник
У меня очень похожая проблема stackoverflow.com/questions/50642922/… . Есть ли способ найти последний элемент списка словаря, удалить его, а затем перейти на уровень выше? Если не удалять, я хочу составить список, в котором последний элемент - это глубина данных, поэтому я
переворачиваю
1

Вот модифицированная версия ответа Фреда Фу для Python 2. В исходном ответе выводится только самый глубокий уровень вложенности. Если вы выводите ключи в виде списков, вы можете сохранить ключи для всех уровней, хотя для того, чтобы ссылаться на них, вам нужно ссылаться на список списков.

Вот функция:

def NestIter(nested):
    for key, value in nested.iteritems():
        if isinstance(value, collections.Mapping):
            for inner_key, inner_value in NestIter(value):
                yield [key, inner_key], inner_value
        else:
            yield [key],value

Чтобы сослаться на ключи:

for keys, vals in mynested: 
    print(mynested[keys[0]][keys[1][0]][keys[1][1][0]])

для трехуровневого словаря.

Вам необходимо знать количество уровней, прежде чем получить доступ к нескольким ключам, и количество уровней должно быть постоянным (можно добавить небольшой фрагмент скрипта для проверки количества уровней вложенности при итерации значений, но я не пока смотрел на это).

Пряный Багет
источник
1

Я считаю этот подход немного более гибким, здесь вы просто предоставляете функцию генератора, которая генерирует пары ключ-значение и может быть легко расширена, чтобы также перебирать списки.

def traverse(value, key=None):
    if isinstance(value, dict):
        for k, v in value.items():
            yield from traverse(v, k)
    else:
        yield key, value

Затем вы можете написать свою собственную myprintфункцию, а затем распечатать эти пары ключ-значение.

def myprint(d):
    for k, v in traverse(d):
        print(f"{k} : {v}")

Тест:

myprint({
    'xml': {
        'config': {
            'portstatus': {
                'status': 'good',
            },
            'target': '1',
        },
        'port': '11',
    },
})

Вывод:

status : good
target : 1
port : 11

Я тестировал это на Python 3.6.

сирекс
источник
0

Эти ответы работают только для 2-х уровней подклассов. Для получения дополнительной информации попробуйте следующее:

nested_dict = {'dictA': {'key_1': 'value_1', 'key_1A': 'value_1A','key_1Asub1': {'Asub1': 'Asub1_val', 'sub_subA1': {'sub_subA1_key':'sub_subA1_val'}}},
                'dictB': {'key_2': 'value_2'},
                1: {'key_3': 'value_3', 'key_3A': 'value_3A'}}

def print_dict(dictionary):
    dictionary_array = [dictionary]
    for sub_dictionary in dictionary_array:
        if type(sub_dictionary) is dict:
            for key, value in sub_dictionary.items():
                print("key=", key)
                print("value", value)
                if type(value) is dict:
                    dictionary_array.append(value)



print_dict(nested_dict)
Jortega
источник