Рекурсивная функция генератора Python вложенные данные JSON

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

Источником данных, передаваемых в функцию генератора, является выход внешнего цикла, который итерирует через коллекцию mongo.

Когда я использовал оператор print в том же месте, что и оператор Yield, я получаю результаты, которые я ожидаю, но когда я переключаю это на оператор yield, генератор, кажется, дает только один элемент на итерацию внешнего цикла.

Надеюсь, кто-то может показать мне, где я иду неправильно.

columns = ['_id'
    , 'name'
    , 'personId'
    , 'status'
    , 'explorerProgress'
    , 'isSelectedForReview'
           ]
db = MongoClient().abcDatabase

coll = db.abcCollection


def dic_recurse(data, fields, counter, source_field):
    counter += 1
    if isinstance(data, dict):
        for k, v in data.items():
            if k in fields and isinstance(v, list) is False and isinstance(v, dict) is False:
                # print "{0}{1}".format(source_field, k)[1:], v
                yield "{0}{1}".format(source_field, k)[1:], v
            elif isinstance(v, list):
                source_field += "_{0}".format(k)
                [dic_recurse(l, fields, counter, source_field) for l in data.get(k)]
            elif isinstance(v, dict):
                source_field += "_{0}".format(k)
                dic_recurse(v, fields, counter, source_field)
    elif isinstance(data, list):
        [dic_recurse(l, fields, counter, '') for l in data]


for item in coll.find():
    for d in dic_recurse(item, columns, 0, ''):
        print d

И ниже приведен пример данных, которые он повторяет, но вложенность увеличивается сверх того, что показано.

{ 
    "_id" : ObjectId("5478464ee4b0a44213e36eb0"), 
    "consultationId" : "54784388e4b0a44213e36d5f", 
    "modules" : [
        {
            "_id" : "FF", 
            "name" : "Foundations", 
            "strategyHeaders" : [
                {
                    "_id" : "FF_Money", 
                    "description" : "Let's see where you're spending your money.", 
                    "name" : "Managing money day to day", 
                    "statuses" : [
                        {
                            "pid" : "54784388e4b0a44213e36d5d", 
                            "status" : "selected", 
                            "whenUpdated" : NumberLong(1425017616062)
                        }, 
                        {
                            "pid" : "54783da8e4b09cf5d82d4e11", 
                            "status" : "selected", 
                            "whenUpdated" : NumberLong(1425017616062)
                        }
                    ], 
                    "strategies" : [
                        {
                            "_id" : "FF_Money_CF", 
                            "description" : "This option helps you get a picture of how much you're spending", 
                            "name" : "Your spending and savings.", 
                            "relatedGoals" : [
                                {
                                    "_id" : ObjectId("54784581e4b0a44213e36e2f")
                                }, 
                                {
                                    "_id" : ObjectId("5478458ee4b0a44213e36e33")
                                }, 
                                {
                                    "_id" : ObjectId("547845a5e4b0a44213e36e37")
                                }, 
                                {
                                    "_id" : ObjectId("54784577e4b0a44213e36e2b")
                                }, 
                                {
                                    "_id" : ObjectId("5478456ee4b0a44213e36e27")
                                }
                            ], 
                            "soaTrashWarning" : "Understanding what you are spending and saving is crucial to helping you achieve your goals. Without this in place, you may be spending more than you can afford. ", 
                            "statuses" : [
                                {
                                    "personId" : "54784388e4b0a44213e36d5d", 
                                    "status" : "selected", 
                                    "whenUpdated" : NumberLong(1425017616062)
                                }, 
                                {
                                    "personId" : "54783da8e4b09cf5d82d4e11", 
                                    "status" : "selected", 
                                    "whenUpdated" : NumberLong(1425017616062)
                                }
                            ], 
                            "trashWarning" : "This option helps you get a picture of how much you're spending and how much you could save.nAre you sure you don't want to take up this option now?nn", 
                            "weight" : NumberInt(1)
                        }, 

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

def dic_recurse(data, fields, counter, source_field):
    print 'Called'
    if isinstance(data, dict):
        for k, v in data.items():
            if isinstance(v, list):
                source_field += "_{0}".format(k)
                [dic_recurse(l, fields, counter, source_field) for l in v]
            elif isinstance(v, dict):
                source_field += "_{0}".format(k)
                dic_recurse(v, fields, counter, source_field)
            elif k in fields and isinstance(v, list) is False and isinstance(v, dict) is False:
                counter += 1
                yield "L{0}_{1}_{2}".format(counter, source_field, k.replace('_', ''))[1:], v
    elif isinstance(data, list):
        for l in data:
            dic_recurse(l, fields, counter, '')

Ключевое различие между двумя версиями при отладке, по-видимому, заключается в том, когда этот раздел кода попадает.

elif isinstance(data, list):
            for l in data:
                dic_recurse(l, fields, counter, '')

Если я тестирую выход версии призыв к dic_recurse(l, fields, counter, '') вам попал, но это не кажется, чтобы вызвать функцию, поскольку любой печати заявления я на открытии функция не бьет, но если я делаю то же самое с помощью печати тогда, когда код попадает в тот же раздел он с радостью вызывает функцию и бежит через всю функцию.

Я уверен, что я, вероятно, неправильно понимаю что-то фундаментальное о генераторах и использовании утверждения yield.

1 ответ

  1. Вместо ответа на это я просто хотел опубликовать мое обновленное решение, если оно окажется полезным для кого-то еще.

    Мне нужно добавить дополнительные операторы yield в функцию, чтобы результат каждого рекурсивного вызова функции генератора мог быть передан для использования следующим, по крайней мере, так я это понял. Счастлив быть исправленным.

    def dic_recurse(data, fields, counter, source_field):
        if isinstance(data, dict):
            counter += 1
            for k, v in data.items():
                if isinstance(v, list):
                    for field_data in v:
                        for list_field in dic_recurse(field_data, fields, counter, source_field):
                            yield list_field
                elif isinstance(v, dict):
                    for dic_field in dic_recurse(v, fields, counter, source_field):
                        yield dic_field
                elif k in fields and isinstance(v, list) is False and isinstance(v, dict) is False:
                    yield counter, {"{0}_L{1}".format(k, counter): v}
        elif isinstance(data, list):
            counter += 1
            for list_item in data:
                for li2 in dic_recurse(list_item, fields, counter, ''):
                    yield li2