解决python字典update浅更新的问题
一、问题复现
python字典中的update使用的是浅拷贝,也就是对于嵌套的内容更新采用的是直接覆盖。演示如下:
if __name__ == '__main__':
d1 = {1: 1231, 2: {123: 2312, 'name': 'zhangs', 'hhh': 'gggg'}, 3: 3242}
d2 = {1: 000, 2: {3: {1, 2, 3, 43}}}
d1.update(d2)
print(d1)
print(d2)
代码执行结果如下:
{1: 0, 2: {3: {1, 2, 3, 43}}, 3: 3242}
{1: 0, 2: {3: {1, 2, 3, 43}}}
很明显对于d1[2]的内容是直接被d2[2]的内容覆盖了,这明显和我们的常理不符合
二、解决方法
使用递归的方法对字典进行更新,这样就可以解决覆盖问题,相应的函数如下:
def update(data: dict, new_data: dict):
""" 对字典进行深度update """
for key, item in new_data.items():
if not data.get(key):
# 如果需要更新字典的key中在原来字典中没有,就添加上
data[key] = new_data[key]
elif isinstance(item, dict):
# 如果需要更新的是字典,就递归更新
update(data[key], new_data[key])
else:
# 如果需要更新的是普通值,就直接修改
data[key] = item
该函数实现了递归更新的功能,效果如下:
if __name__ == '__main__':
d1 = {1: 1231, 2: {123: 2312, 'name': 'zhangs', 'hhh': 'gggg'}, 3: 3242}
d2 = {1: 000, 2: {3: {1, 2, 3, 43}}}
# d1.update(d2)
update(d1, d2)
print(d1)
print(d2)
运行结果:
{1: 0, 2: {123: 2312, 'name': 'zhangs', 'hhh': 'gggg', 3: {1, 2, 3, 43}}, 3: 3242}
{1: 0, 2: {3: {1, 2, 3, 43}}}
三、优化之处
上面的代码已经实现了深度更新的功能,但还是存在一部分问题。
问题描述:上述代码对于d1原本没有的key是采用直接赋值,这样对于基本数据类型还好说,但对于引用类型的数据就存在问题。因为是把d2的引用赋值给了d1,如果d2对应的内容修改了,那么d1的内容也会发生变化。
问题复现
if __name__ == '__main__':
d1 = {1: 1231, 2: {123: 2312, 'name': 'zhangs', 'hhh': 'gggg'}, 3: 3242}
d2 = {1: 000, 2: {3: {1, 2, 3, 43}}}
update(d1, d2)
print('修改d2前d1的值', d1)
d2[2][3].remove(43)
print('修改d2后d1的值', d1)
运行结果如下:
修改d2前d1的值 {1: 0, 2: {123: 2312, 'name': 'zhangs', 'hhh': 'gggg', 3: {1, 2, 3, 43}}, 3: 3242}
修改d2后d1的值 {1: 0, 2: {123: 2312, 'name': 'zhangs', 'hhh': 'gggg', 3: {1, 2, 3}}, 3: 3242}
这两次打印的都是d1的值,在代码中也没有修改d1的值,但这两次的结果确不一样,这就是对应引用数据类型直接赋值会产生的问题
优化代码
对于该问题,可以使用应用类型的copy方法解决,代码如下:
def update(data: dict, new_data: dict):
""" 对字典进行深度update """
for key, item in new_data.items():
if not data.get(key):
# 如果需要更新字典的key中在原来字典中没有,就添加上
# data[key] = new_data[key]
data[key] = new_data[key].copy() if hasattr(new_data[key], 'copy') else new_data[key]
elif isinstance(item, dict):
# 如果需要更新的是字典,就递归更新
update(data[key], new_data[key])
else:
# 如果需要更新的是普通值,就直接修改
data[key] = item
优化后该问题就不存在了,当然copy也是浅拷贝,也会存在相应的问题,但优化的思路一致,这里就不复述了。
四、相关代码
def update(data: dict, new_data: dict):
""" 对字典进行深度update """
for key, item in new_data.items():
if not data.get(key):
# 如果需要更新字典的key中在原来字典中没有,就添加上
# data[key] = new_data[key]
data[key] = new_data[key].copy() if hasattr(new_data[key], 'copy') else new_data[key]
elif isinstance(item, dict):
# 如果需要更新的是字典,就递归更新
update(data[key], new_data[key])
else:
# 如果需要更新的是普通值,就直接修改
data[key] = item
如果不需要解决引用类型的问题,直接取消掉注释的代码即可,具体修改看上述文章所写内容。