Перегляньте всі вкладені значення словника?


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

Я намагаюся прокрутити словник і роздрукувати всі пари ключових значень, де значення не є вкладеним словником. Якщо значення є словником, я хочу зайти до нього та роздрукувати його пари ключових значень ... тощо. Будь-яка допомога?

EDIT

Як щодо цього? Він досі друкує лише одне.

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'}

1
Здається, ви хочете рекурсії, але опис недостатньо зрозумілий, щоб бути впевненим. А як щодо прикладу вводу / виводу? Крім того, що не так з кодом?
Ніклас Б.

2
В Python є фіксований обмеження рекурсії: docs.python.org/library/sys.html#sys.setrecursionlimit
д-р Ян-Філіп Геркке

2
@ Jan-PhilipGehrcke: Реалізувати алгоритми на деревоподібній структурі даних без рекурсії - це самогубство.
Ніклас Б.

2
@Takkun: Ви використовуєте dictяк ім'я змінної. Ніколи цього не робіть (ось чому це не вдається).
Ніклас Б.

3
@ NiklasB., Re: "самогубство": Я щойно реалізував ітераційну версію алгоритму Шаррона та його лише два рядки довше і все ще досить легко дотримуватися. Крім того, переклад рекурсії на ітерацію часто є обов'язковою умовою при переході від дерев до загальних графіків.
Фред Фоо

Відповіді:


157

Як сказав Ніклас, вам потрібна рекурсія, тобто ви хочете визначити функцію для друку свого дикту, і якщо значенням є дикт, ви хочете викликати функцію друку за допомогою цього нового дикта.

Щось на зразок :

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

3
невелике поліпшення. додати print (k), перш ніж викликати myprint (v).
Наомі Фрідман

З рекурсією це банально.
серджач

36

Можливі проблеми, якщо ви пишете власну рекурсивну реалізацію або ітеративний еквівалент зі стеком. Дивіться цей приклад:

    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-nary tree. Але визначення не виключає можливості поперечного краю або навіть заднього краю (таким чином, це вже не дерево). Наприклад, тут 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 .

Аналогічно, ви отримуєте нескінченний цикл із цією реалізацією від Fred Foo :

    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
Дякуємо, що вказали на це. Будьте проти включення коду у відповідь. Я думаю, що це завершує вашу чудову відповідь.
Мондра

Для Python3 використовуйте list(d.items())як d.items()повертає подання, а не список, а використовуйте v.items()замістьv.iteritems()
Макс

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

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чи навіть пріоритетну чергу.
Фред Фоо

Так, має сенс. Дякую та щасливе кодування :)
Ніклас Б.

Так, але це рішення займає більше місця, ніж моє та рекурсивне.
шламар

1
@ ms4py: для розваги я створив орієнтир . На моєму комп’ютері рекурсивна версія найшвидша, і larsmans є другою для всіх трьох тестових словників. Версія, що використовує генератори, є відносно повільною, як і очікувалося (тому що їй доводиться робити багато жонглювання з різними генераторськими контекстами)
Ніклас Б.

9

Трохи інша версія, яку я написав, що відслідковує ключі по дорозі, щоб туди потрапити

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'

Його також легко змінити, щоб відстежувати префікс як набір ключів, а не рядок, якщо вам це потрібно.


Як додати вихід до списку?
Шаш

5

Ось пітонічний спосіб це зробити. Ця функція дозволить переглядати пару ключових значень на всіх рівнях. Це не зберігає все в пам’яті, а швидше проходить по картинці, коли ви проходите через нього

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

Як у тому, що? Big O має бути однаковим (це O(depth)стосується рекурсивного рішення. Те саме стосується і цієї версії, якщо я правильно думаю).
Ніклас Б.

"Скопіювати стек"? Про що ти говориш? Кожен виклик функції створює новий стек-кадр. Ваше рішення використовує itersяк явний стек, тому споживання пам’яті Big-O те саме, чи я щось пропускаю?
Ніклас Б.

@NiklasB. Рекурсія завжди йде з накладними витратами, детальніше див. Цей розділ у Вікіпедії: en.wikipedia.org/wiki/… Рамка стеку рекурсивного рішення набагато більша.
шламар

Ви повинні нерозуміти цей параграф. Це не говорить нічого, що підтверджує ваші заяви.
Ніклас Б.

1
@NiklasB. Ні, тому що тут кадр стека - це лише ітер, а для рекурсивного рішення кадр стека має ітер, програмний лічильник, змінне середовище тощо ...
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/… . Чи є спосіб знайти останній елемент списку словника, видалити його та перенести рівень вгору? Якщо не видалити, я хочу скласти список, де останній елемент - це глибина даних, тому я перевернув список та видалив
Heenashree Khandelwal,

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

Ці відповіді працюють лише для двох рівнів під словників. Щоб дізнатися більше, спробуйте:

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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.