Lặp qua tất cả các giá trị từ điển lồng nhau?


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

Tôi đang cố gắng lặp lại từ điển và in ra tất cả các cặp giá trị khóa mà giá trị không phải là từ điển lồng nhau. Nếu giá trị là một từ điển, tôi muốn truy cập vào nó và in ra các cặp giá trị khóa của nó ... vv. Bất kỳ giúp đỡ?

BIÊN TẬP

Còn cái này thì sao? Nó vẫn chỉ in một thứ.

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

Trường hợp thử nghiệm đầy đủ

Từ điển:

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

Kết quả:

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

1
Có vẻ như bạn muốn đệ quy, nhưng mô tả không đủ rõ ràng để chắc chắn. Điều gì về một số ví dụ trong- / đầu ra? Ngoài ra, mã của bạn có gì sai?
Niklas B.

2
Có một giới hạn đệ quy cố định trong Python: docs.python.org/library/sys.html#sys.setrecursionlimit
Tiến sĩ Jan-Philip Gehrcke

2
@ Jan-PhilipGehrcke: Để thực hiện các thuật toán trên cấu trúc dữ liệu dạng cây mà không có đệ quy là một sự tự sát rõ ràng.
Niklas B.

2
@Takkun: Bạn đang sử dụng dictlàm tên biến. Đừng bao giờ làm điều này (đây là lý do tại sao nó không thành công).
Niklas B.

3
@NiklasB., Lại: "tự sát": Tôi vừa triển khai một phiên bản lặp đi lặp lại của thuật toán Scharron và chỉ dài hơn hai dòng của nó và vẫn khá dễ làm theo. Bên cạnh đó, việc dịch đệ quy sang lặp thường là một yêu cầu khi đi từ dạng cây sang đồ thị tổng quát.
Fred Foo

Câu trả lời:


157

Như Niklas đã nói, bạn cần đệ quy, tức là bạn muốn xác định một hàm để in ra dict của mình, và nếu giá trị là một dict, bạn muốn gọi hàm in của mình bằng cách sử dụng dict mới này.

Cái gì đó như :

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

3
cải tiến nhỏ. thêm print (k), trước khi gọi myprint (v).
Naomi Fridman

Với đệ quy, điều đó thật tầm thường.
sergzach

36

những vấn đề tiềm ẩn nếu bạn viết triển khai đệ quy của riêng mình hoặc tương đương lặp lại với ngăn xếp. Xem ví dụ này:

    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

Theo nghĩa thông thường, từ điển lồng nhau sẽ là một cây n-nary giống như cấu trúc dữ liệu. Nhưng định nghĩa này không loại trừ khả năng có một cạnh chéo hoặc thậm chí là một cạnh sau (do đó không còn là cây nữa). Ví dụ, ở đây key2.2 giữ từ điển từ key1 , key2.3 trỏ đến toàn bộ từ điển (cạnh sau / chu kỳ). Khi có một cạnh quay lại (chu kỳ), ngăn xếp / đệ quy sẽ chạy vô hạn.

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

Nếu bạn in từ điển này với sự triển khai này từ Scharron

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

Bạn sẽ thấy lỗi này:

    RuntimeError: maximum recursion depth exceeded while calling a Python object

Tương tự với việc triển khai từ người gửi .

Tương tự, bạn nhận được một vòng lặp vô hạn với việc triển khai này từ 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))

Tuy nhiên, Python thực sự phát hiện các chu trình trong từ điển lồng nhau:

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

"{...}" là nơi phát hiện một chu kỳ.

Theo yêu cầu của Moondra, đây là một cách để tránh chu kỳ (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)

vậy thì bạn sẽ triển khai một giải pháp lặp lại như thế nào?
dreftymac

2
@dreftymac tôi sẽ thêm một bộ thăm cho các phím để tránh xảy ra chu kỳ: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
Cảm ơn vì đã chỉ ra điều này. Bạn có phiền bao gồm mã của bạn trong câu trả lời. Tôi nghĩ rằng nó hoàn thành câu trả lời xuất sắc của bạn.
Moondra

Đối với Python3, sử dụng list(d.items())dưới dạng d.items()trả về một dạng xem, không phải danh sách và sử dụng v.items()thay vìv.iteritems()
Tối đa

33

Vì a dictcó thể lặp lại, bạn có thể áp dụng công thức có thể lặp lại vùng chứa lồng nhau cổ điển cho vấn đề này chỉ với một vài thay đổi nhỏ. Đây là phiên bản Python 2 (xem bên dưới cho 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

Kiểm tra:

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

Trong Python 2, Có thể tạo một tùy chỉnh Mappingđủ điều kiện là một Mappingnhưng không chứa iteritems, trong trường hợp này, điều này sẽ không thành công. Tài liệu không chỉ ra rằng điều đó iteritemslà bắt buộc đối vớiMapping ; mặt khác, nguồn cung cấp cho Mappingcác loại một iteritemsphương thức. Vì vậy, đối với tùy chỉnh Mappings, kế thừa từ collections.Mappingrõ ràng chỉ trong trường hợp.

Trong Python 3, có một số cải tiến cần được thực hiện. Kể từ Python 3.3, các lớp cơ sở trừu tượng nằm trong đó collections.abc. Chúng vẫn ở trong collectionstính tương thích ngược, nhưng sẽ tốt hơn nếu có các lớp cơ sở trừu tượng của chúng ta cùng nhau trong một không gian tên. Vì vậy, điều này nhập khẩu abctừ collections. Python 3.3 cũng cho biết thêm yield from, được thiết kế cho những loại tình huống này. Đây không phải là đường cú pháp rỗng; nó có thể dẫn đến mã nhanh hơn và tương tác hợp lý hơn với các coroutines .

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)không có gì đảm bảo hasattr(item, "iteritems"). Kiểm tra collections.Mappinglà tốt hơn.
Fred Foo

1
@larsmans, tất nhiên là bạn nói đúng. Tôi đã nghĩ rằng việc sử dụng Iterablesẽ làm cho giải pháp này tổng quát hơn, nhưng quên rằng, rõ ràng, các mục lặp không nhất thiết phải có iteritems.
gửi

+1 cho câu trả lời này bởi vì nó là giải pháp chung hiệu quả cho vấn đề này, nhưng nó không bị hạn chế chỉ in các giá trị. @Takkun bạn chắc chắn nên xem xét tùy chọn này. Về lâu dài, bạn sẽ muốn nhiều hơn là chỉ in các giá trị.
Alejandro Piad

1
@ Seanny123, Cảm ơn vì đã thu hút sự chú ý của tôi đến điều này. Trên thực tế, Python 3 thay đổi hình ảnh theo một vài cách - tôi sẽ viết lại điều này như một phiên bản sử dụng yield fromcú pháp mới .
gửi

25

Giải pháp lặp lại thay thế:

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
Vâng, đó là cách tôi tưởng tượng nó trông như thế nào. Cảm ơn. Vì vậy, lợi thế của điều này là nó sẽ không tràn ngăn xếp đối với các tổ cực sâu? Hay là có cái gì khác với nó?
Niklas B.

@NiklasB: vâng, đó là lợi ích đầu tiên. Ngoài ra, phiên bản này có thể được điều chỉnh theo các thứ tự duyệt khác nhau khá dễ dàng bằng cách thay thế ngăn xếp (a list) bằng một dequehoặc thậm chí là hàng đợi ưu tiên.
Fred Foo

Đúng, có lý. Cảm ơn bạn và chúc bạn viết mã vui vẻ :)
Niklas B.

Có, nhưng giải pháp này tốn nhiều dung lượng hơn giải pháp của tôi và giải pháp đệ quy.
schlamar

1
@ ms4py: Để cho vui, tôi đã tạo một điểm chuẩn . Trên máy tính của tôi, phiên bản đệ quy là nhanh nhất và larsmans đứng thứ hai cho cả ba từ điển thử nghiệm. Phiên bản sử dụng máy phát điện là tương đối chậm, như mong đợi (vì nó đã làm rất nhiều tung hứng với bối cảnh phát khác nhau)
Niklas B.

9

Phiên bản hơi khác mà tôi đã viết theo dõi các phím trên đường đến đó

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

Trên dữ liệu của bạn, nó sẽ in

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

Cũng dễ dàng sửa đổi nó để theo dõi tiền tố dưới dạng một bộ khóa chứ không phải một chuỗi nếu bạn cần theo cách đó.


Làm cách nào để thêm đầu ra vào danh sách?
Shash,

5

Đây là cách pythonic để làm điều đó. Chức năng này sẽ cho phép bạn lặp lại cặp khóa-giá trị trong tất cả các cấp. Nó không lưu toàn bộ nội dung vào bộ nhớ mà thay vào đó là lướt qua bài đọc khi bạn lặp lại nó

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)

Bản in

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

3

Một giải pháp thay thế để làm việc với các danh sách dựa trên giải pháp của 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)

2

Giải pháp lặp lại như một giải pháp thay thế:

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

Làm như thế nào? Big O nên giống nhau (đó là O(depth)giải pháp đệ quy. Điều tương tự cũng áp dụng cho phiên bản này, nếu tôi nghĩ đúng).
Niklas B.

"Sao chép ngăn xếp"? Bạn đang nói về cái gì Mỗi lệnh gọi hàm sẽ tạo ra một khung xếp chồng mới. Giải pháp của bạn sử dụng itersnhư một ngăn xếp rõ ràng, vì vậy mức tiêu thụ bộ nhớ Big-O là như nhau, hay tôi đang thiếu thứ gì đó?
Niklas B.

@NiklasB. Đệ quy luôn đi kèm với chi phí, xem phần này tại Wikipedia để biết thêm chi tiết: en.wikipedia.org/wiki/… Khung ngăn xếp của giải pháp đệ quy lớn hơn nhiều.
schlamar

Chắc bạn đang hiểu sai đoạn văn đó. Nó không nói lên bất cứ điều gì để hỗ trợ các tuyên bố của bạn.
Niklas B.

1
@NiklasB. Không, bởi vì khung ngăn xếp ở đây chỉ là iter và đối với giải pháp đệ quy, khung ngăn xếp có iter, bộ đếm chương trình, môi trường biến, v.v.
schlamar

2

Tôi đang sử dụng mã sau để in tất cả các giá trị của từ điển lồng nhau, có tính đến nơi giá trị có thể là danh sách chứa từ điển. Điều này hữu ích với tôi khi phân tích cú pháp tệp JSON thành từ điển và cần nhanh chóng kiểm tra xem có bất kỳ giá trị nào của nó hay không 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)

Đầu ra:

    10
    2017-03-15T14:02:49.301000
    bar
    some_string

Tôi gặp nhiều vấn đề tương tự ở đây stackoverflow.com/questions/50642922/… . Có cách nào để tìm phần tử cuối cùng của danh sách từ điển, xóa phần tử đó và sau đó tăng cấp không? Nếu không xóa, tôi muốn thực hiện một danh sách nơi yếu tố cuối cùng là độ sâu của dữ liệu vì vậy tôi đảo ngược danh sách và xóa
Heenashree Khandelwal

1

Đây là phiên bản sửa đổi của câu trả lời của Fred Foo cho Python 2. Trong câu trả lời ban đầu, chỉ mức lồng ghép sâu nhất là đầu ra. Nếu bạn xuất các khóa dưới dạng danh sách, bạn có thể giữ các khóa cho tất cả các cấp, mặc dù để tham chiếu chúng, bạn cần tham chiếu danh sách các danh sách.

Đây là chức năng:

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

Để tham chiếu các khóa:

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

cho một từ điển ba cấp.

Bạn cần biết số cấp trước đó để truy cập nhiều khóa và số cấp phải không đổi (có thể thêm một đoạn nhỏ tập lệnh để kiểm tra số cấp lồng nhau khi lặp qua các giá trị, nhưng tôi chưa chưa nhìn vào điều này).


1

Tôi thấy cách tiếp cận này linh hoạt hơn một chút, ở đây bạn chỉ cần cung cấp hàm trình tạo phát ra các cặp khóa, giá trị và có thể dễ dàng mở rộng để cũng lặp qua danh sách.

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

Sau đó, bạn có thể viết myprinthàm của riêng mình , rồi in các cặp giá trị khóa đó.

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

Bài kiểm tra:

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

Đầu ra:

status : good
target : 1
port : 11

Tôi đã thử nghiệm điều này trên Python 3.6.


0

Những câu trả lời này chỉ hoạt động cho 2 cấp độ của từ điển phụ. Để biết thêm, hãy thử cái này:

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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.