Ulangi semua nilai kamus bersarang?

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

Saya mencoba mengulang melalui kamus dan mencetak semua pasangan nilai kunci yang nilainya bukan kamus bersarang. Jika nilainya adalah kamus, saya ingin membacanya dan mencetak pasangan nilai kuncinya ... dll. Ada bantuan?

EDIT

Bagaimana dengan ini? Itu masih hanya mencetak satu hal.

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

Kasus Uji Penuh

Kamus:

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

Hasil:

xml : {u'config': {u'portstatus': {u'status': u'good'}, u'target': u'1'}, u'port': u'11'}
Takkun
sumber
1
Kedengarannya Anda ingin rekursi, tetapi deskripsinya tidak cukup jelas untuk memastikannya. Bagaimana dengan beberapa contoh in- / output? Juga, apa yang salah dengan kode Anda?
Niklas B.
2
Ada batas rekursi tetap dalam Python: docs.python.org/library/sys.html#sys.setrecursionlimit
Dr. Jan-Philip Gehrcke
2
@ Jan-PhilipGehrcke: Untuk mengimplementasikan algoritma pada struktur data seperti pohon tanpa rekursi sama saja dengan bunuh diri.
Niklas B.
2
@Takkun: Anda menggunakan dictsebagai nama variabel. Jangan pernah melakukan ini (inilah mengapa gagal).
Niklas B.
3
@NiklasB., Re: "bunuh diri": Saya baru saja menerapkan versi berulang dari algoritme Scharron dan hanya dua baris yang lebih panjang dan masih cukup mudah diikuti. Selain itu, menerjemahkan rekursi ke iterasi sering menjadi persyaratan saat beralih dari pohon ke grafik umum.
Fred Foo

Jawaban:

157

Seperti yang dikatakan oleh Niklas, Anda memerlukan rekursi, yaitu Anda ingin menentukan fungsi untuk mencetak dict Anda, dan jika nilainya adalah dict, Anda ingin memanggil fungsi cetak Anda menggunakan dict baru ini.

Sesuatu seperti :

def myprint(d):
    for k, v in d.items():
        if isinstance(v, dict):
            myprint(v)
        else:
            print("{0} : {1}".format(k, v))
Scharron
sumber
3
perbaikan kecil. tambahkan print (k), sebelum memanggil myprint (v).
Naomi Fridman
Dengan rekursi itu sepele.
sergzach
36

Ada potensi masalah jika Anda menulis implementasi rekursif Anda sendiri atau yang setara berulang dengan stack. Lihat contoh ini:

    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

Dalam pengertian normal, kamus bersarang akan menjadi pohon n-nary seperti struktur data. Tetapi definisi tersebut tidak mengecualikan kemungkinan tepi silang atau bahkan tepi belakang (dengan demikian bukan lagi pohon). Misalnya, di sini key2.2 memegang kamus dari key1 , key2.3 menunjuk ke seluruh kamus (tepi belakang / siklus). Ketika ada back edge (siklus), stack / rekursi akan berjalan tanpa batas.

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

Jika Anda mencetak kamus ini dengan implementasi ini dari Scharron

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

Anda akan melihat kesalahan ini:

    RuntimeError: maximum recursion depth exceeded while calling a Python object

Hal yang sama berlaku untuk implementasi dari senderle .

Demikian pula, Anda mendapatkan loop tak terbatas dengan implementasi ini dari 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))

Namun, Python sebenarnya mendeteksi siklus dalam kamus bersarang:

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

"{...}" adalah tempat sebuah siklus terdeteksi.

Seperti yang diminta oleh Moondra, ini adalah cara untuk menghindari siklus (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)
tengr
sumber
jadi bagaimana Anda akan menerapkan solusi berulang?
dreftymac
2
@dreftymac Saya akan menambahkan satu set yang dikunjungi untuk kunci untuk menghindari siklus: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
Terima kasih telah menunjukkan hal ini. Maukah Anda memasukkan kode Anda dalam jawaban. Saya pikir itu melengkapi jawaban Anda yang luar biasa.
Moondra
Untuk Python3, gunakan list(d.items())sebagai d.items()mengembalikan tampilan, bukan daftar, dan gunakan v.items()sebagai gantiv.iteritems()
Maks
33

Karena a dictiterable, Anda bisa menerapkan rumus iterable container bersarang klasik untuk masalah ini dengan hanya beberapa perubahan kecil. Ini adalah versi Python 2 (lihat di bawah untuk 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

Uji:

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)]

Dalam Python 2, Ini mungkin menjadi mungkin untuk membuat kustom Mappingyang memenuhi syarat sebagai Mappingtetapi tidak mengandung iteritems, dalam hal ini akan gagal. Dokumen tidak menunjukkan bahwa iteritemsdiperlukan untuk a Mapping; di sisi lain, sumber memberikan Mappingtipe iteritemsmetode. Jadi untuk custom Mappings, mewarisi dari collections.Mappingsecara eksplisit untuk berjaga-jaga.

Di Python 3, ada sejumlah perbaikan yang harus dilakukan. Pada Python 3.3, kelas dasar abstrak tinggal collections.abc. Mereka tetap ada collectionsuntuk kompatibilitas mundur, tetapi lebih baik memiliki kelas dasar abstrak kita bersama dalam satu namespace. Jadi ini mengimpor abcdari collections. Python 3.3 juga menambahkan yield from, yang dirancang hanya untuk situasi semacam ini. Ini bukan gula sintaksis kosong; ini dapat menghasilkan kode yang lebih cepat dan interaksi yang lebih masuk akal dengan coroutine .

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
pengirim
sumber
3
isinstance(item, collections.Iterable)tidak ada jaminan untuk hasattr(item, "iteritems"). Memeriksa collections.Mappinglebih baik.
Fred Foo
1
@larsmans, Anda benar, tentu saja. Saya berpikir bahwa menggunakan Iterableakan membuat solusi ini lebih digeneralisasi, melupakan bahwa, jelas, iterable tidak harus begitu iteritems.
senderle
+1 untuk jawaban ini karena ini adalah solusi umum yang berfungsi untuk masalah ini, tetapi tidak terbatas hanya mencetak nilai. @Takkun Anda pasti harus mempertimbangkan opsi ini. Dalam jangka panjang, Anda akan menginginkan lebih dari sekadar mencetak nilainya.
Alejandro Piad
1
@ Seanny123, Terima kasih telah menarik perhatian saya untuk ini. Python 3 mengubah gambar dalam beberapa cara, sebenarnya - saya akan menulis ulang ini sebagai versi yang menggunakan yield fromsintaks baru .
pengirim
25

Solusi iteratif alternatif:

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))
Fred Foo
sumber
2
Ya, begitulah yang kubayangkan. Terima kasih. Jadi keuntungannya adalah tidak akan meluap tumpukan untuk sarang yang sangat dalam? Atau ada hal lain di dalamnya?
Niklas B.
@NiklasB .: ya, itu manfaat pertama. Selain itu, versi ini dapat disesuaikan dengan urutan traversal yang berbeda dengan cukup mudah dengan mengganti tumpukan (a list) dengan dequeatau bahkan antrian prioritas.
Fred Foo
Ya, masuk akal. Terima kasih dan selamat coding :)
Niklas B.
Ya, tetapi solusi ini lebih memakan ruang daripada solusi saya dan rekursif.
schlamar
1
@ ms4py: Untuk bersenang-senang, saya membuat benchmark . Di komputer saya, versi rekursif adalah yang tercepat dan larsman adalah yang kedua untuk ketiga kamus uji. Versi yang menggunakan generator relatif lambat, seperti yang diharapkan (karena harus banyak menyulap dengan konteks generator yang berbeda)
Niklas B.
9

Versi yang sedikit berbeda saya tulis yang melacak kunci di sepanjang jalan menuju ke sana

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)))

Pada data Anda, itu akan dicetak

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

Ini juga mudah untuk memodifikasinya untuk melacak awalan sebagai tupel kunci daripada string jika Anda membutuhkannya seperti itu.

Ehsan Kia
sumber
Bagaimana cara menambahkan output ke daftar?
Shash
5

Inilah cara pythonic untuk melakukannya. Fungsi ini akan memungkinkan Anda untuk melakukan loop melalui key-value pair di semua level. Itu tidak menyimpan semuanya ke memori melainkan berjalan melalui dict saat Anda mengulanginya

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)

Cetakan

a {1: {1: 2, 3: 4}, 2: {5: 6}}
1 {1: 2, 3: 4}
1 2
3 4
2 {5: 6}
5 6
Dmitry Torba
sumber
3

Solusi alternatif untuk bekerja dengan daftar berdasarkan solusi Scharron

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)
Gabriel
sumber
2

Solusi berulang sebagai alternatif:

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.dll
sumber
Bagaimana itu? Big O harus sama (ini O(depth)untuk solusi rekursif. Hal yang sama berlaku untuk versi ini, jika saya berpikir dengan benar).
Niklas B.
"Salin tumpukannya"? Apa yang kamu bicarakan? Setiap panggilan fungsi membuat stackframe baru. Solusi Anda digunakan iterssebagai tumpukan eksplisit, jadi konsumsi memori Big-O sama, atau saya kehilangan sesuatu?
Niklas B.
@Niklas. Rekursi selalu disertai dengan overhead, lihat bagian ini di Wikipedia untuk detailnya: en.wikipedia.org/wiki/… Bingkai tumpukan dari solusi rekursif jauh lebih besar.
schlamar
Anda pasti salah paham tentang paragraf itu. Itu tidak mengatakan apa pun untuk mendukung pernyataan Anda.
Niklas B.
1
@Niklas. Tidak, karena stack frame di sini hanya iter dan untuk solusi rekursif frame stack memiliki iter, program counter, lingkungan variabel, dll ...
schlamar
2

Saya menggunakan kode berikut untuk mencetak semua nilai kamus bersarang, dengan mempertimbangkan di mana nilainya bisa berupa daftar yang berisi kamus. Ini berguna bagi saya saat mengurai file JSON ke dalam kamus dan perlu memeriksa dengan cepat apakah ada nilainya 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)

Keluaran:

    10
    2017-03-15T14:02:49.301000
    bar
    some_string
sigma
sumber
Saya memiliki banyak masalah serupa di sini stackoverflow.com/questions/50642922/… . Apakah ada cara untuk menemukan elemen terakhir dari daftar kamus, menghapusnya dan kemudian naik level? Jika tidak menghapus, saya ingin membuat daftar di mana elemen terakhir adalah kedalaman data jadi saya membalikkan daftar dan menghapus
Heenashree Khandelwal
1

Berikut adalah versi modifikasi dari jawaban Fred Foo untuk Python 2. Dalam respons asli, hanya tingkat bersarang yang paling dalam yang dihasilkan. Jika Anda mengeluarkan kunci sebagai daftar, Anda dapat menyimpan kunci untuk semua level, meskipun untuk mereferensikannya Anda perlu merujuk daftar daftar.

Berikut fungsinya:

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

Untuk mereferensikan kunci:

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

untuk kamus tiga tingkat.

Anda perlu mengetahui jumlah level sebelum mengakses beberapa kunci dan jumlah level harus konstan (dimungkinkan untuk menambahkan sedikit skrip untuk memeriksa jumlah level bersarang saat mengulang nilai, tetapi saya belum belum melihat ini).

SpicyBaguette
sumber
1

Saya menemukan pendekatan ini sedikit lebih fleksibel, di sini Anda hanya menyediakan fungsi generator yang memancarkan kunci, pasangan nilai dan dapat dengan mudah diperluas untuk juga mengulang daftar.

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

Kemudian Anda dapat menulis myprintfungsi Anda sendiri , lalu mencetak pasangan nilai kunci tersebut.

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

Sebuah tes:

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

Keluaran:

status : good
target : 1
port : 11

Saya menguji ini pada Python 3.6.

sirex
sumber
0

Jawaban ini hanya berfungsi untuk 2 level sub-kamus. Untuk lebih lanjut coba ini:

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
sumber