Python折叠/减少多个词典的组成

jhr*_*hrr 7 python reduce dictionary fold

我想实现以下目标.它本质上是任意数量的字典的组合或合并,参考"种子"或根字典,在最终结果中累积所有未更改和更新的值.

seed = {
    'update': False,
    'data': {
        'subdata': {
            'field1': 5,
            'field2': '2018-01-30 00:00:00'
        },
        'field3': 2,
        'field4': None
    },
    'data_updates': {},
    'subdata_updates': {},
    'diffs': {}
}

update_1 = {
    'update': True,
    'data': {
        'subdata': {
            'field1': 6,
            'field2': '2018-01-30 00:00:00'
        },
        'field3': 2,
        'field4': None
    },
    'data_updates': {},
    'subdata_updates': {'field1': 6},
    'diffs': {
        'field1': {
            'field': 'field1',
            'before': 5,
            'after': 6
        }
    }
}

update_2 = {
    'update': True,
    'data': {
        'subdata': {
            'field1': 5,
            'field2': '2018-01-30 00:00:00',
        },
        'field3': 2,
        'field4': 1
    },
    'data_updates': {'field4': 1},
    'subdata_updates': {},
    'diffs': {
        'field4': {
            'field': 'field4',
            'before': None,
            'after': 1
        }
    }
}

# I want to be able to pass in an arbitrary number of updates.
assert reduce_maps(seed, *[update_1, update_2]) == {
    'update': True,
    'data': {
        'subdata': {
            'field1': 6,
            'field2': '2018-01-30 00:00:00',
        },
        'field3': 2,
        'field4': 1
    },
    'data_updates': {'field4': 1},
    'subdata_updates': {'field1': 6},
    'diffs': {
        'field1': {
            'field': 'field1',
            'before': 5,
            'after': 6
        },
        'field4': {
            'field': 'field4',
            'before': None,
            'after': 1
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以假设数据始终处于此形状,您还可以假设每个有效负载仅更新一个字段,并且没有两个更新将更新同一字段.

我可以模糊地看到背景中潜伏的折叠类似物,在这里建立数据seed.

因为数据总是相同的形状,所以手动编写函数来遍历有效负载并手动将它们添加到累加器中并不困难,但我想知道是否有更好的,更通用的方法来执行此操作我可能不会意识到.我希望seed可能会这样,就像这样

seed = {
    'update': False,
    'data': {
        'subdata': {
            'field1': 5,
            'field2': '2018-01-30 00:00:00'
        },
        'field3': 2,
        'field4': None
    },
    'data_updates': {},
    'subdata_updates': {},
    'diffs': {}
}

update_1 = {
    'update': True,
    'data': {
        'subdata': {
            'field1': 6,
            'field2': '2018-01-30 00:00:00'
        },
        'field3': 2,
        'field4': None
    },
    'data_updates': {},
    'subdata_updates': {'field1': 6},
    'diffs': {
        'field1': {
            'field': 'field1',
            'before': 5,
            'after': 6
        }
    }
}

update_2 = {
    'update': True,
    'data': {
        'subdata': {
            'field1': 5,
            'field2': '2018-01-30 00:00:00',
        },
        'field3': 2,
        'field4': 1
    },
    'data_updates': {'field4': 1},
    'subdata_updates': {},
    'diffs': {
        'field4': {
            'field': 'field4',
            'before': None,
            'after': 1
        }
    }
}

# I want to be able to pass in an arbitrary number of updates.
assert reduce_maps(seed, *[update_1, update_2]) == {
    'update': True,
    'data': {
        'subdata': {
            'field1': 6,
            'field2': '2018-01-30 00:00:00',
        },
        'field3': 2,
        'field4': 1
    },
    'data_updates': {'field4': 1},
    'subdata_updates': {'field1': 6},
    'diffs': {
        'field1': {
            'field': 'field1',
            'before': 5,
            'after': 6
        },
        'field4': {
            'field': 'field4',
            'before': None,
            'after': 1
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但这只返回第一个映射,并丢弃其他映射.

jhr*_*hrr 0

import copy
from functools import partial, reduce

def traverse(seed, update, sentinel):
    for key, value in update.items():
        if isinstance(value, dict):
            try:
                traverse(seed[key], update[key], sentinel)
            except KeyError:
                seed[key] = value
        else:
            if key not in seed or value != seed[key] \
                    and key not in sentinel:
                seed[key] = value
                sentinel.add(key)
    return seed


def reduce_maps(seed, *updates):
    seed = copy.deepcopy(seed)
    return reduce(
        partial(traverse, sentinel=set()), [seed, *updates]
    )
Run Code Online (Sandbox Code Playgroud)