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


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.