HOWTO · Python

如何在 Python 中复制一个字典

本文说明 Python 字典赋值如何创建别名、浅拷贝何时足够,以及何时需要深拷贝才能让嵌套值彼此独立。

本页内容

要创建新的外层字典,可以使用 dict(source) 或 source.copy()。两者都是浅拷贝,嵌套值仍然共享。若嵌套的可变值也需要彼此独立,请使用 copy.deepcopy(source)。使用 = 赋值不会创建副本。更多背景信息请参阅 Python 字典教程 和 Python 的 copy 模块文档。

给字典赋值会创建别名

赋值会将另一个名称绑定到同一个字典对象。通过任一名称修改字典,另一个名称也会看到这些更改。重新绑定名称则不同:它会让该名称指向别处,而不会更改原对象或另一个名称所指向的对象。

下面这个完整示例先通过 meal 修改共享字典,然后再将 meal 重新绑定到一个新字典:

food = {"Fruit": "Orange", "Vegetable": "Lettuce"}
meal = food
meal["Fruit"] = "Banana"
print(food)
print(meal)
meal = {"Fruit": "Apple"}
print(food)
print(meal)
{'Fruit': 'Banana', 'Vegetable': 'Lettuce'}
{'Fruit': 'Banana', 'Vegetable': 'Lettuce'}
{'Fruit': 'Banana', 'Vegetable': 'Lettuce'}
{'Fruit': 'Apple'}

前两个打印出的值相同,因为在这一刻两个名称都指向同一个对象。将新字典赋给 meal 后,只有 meal 指向新创建的字典;food 仍然指向原来的字典。理解这一区别,就能明白为什么通过一个名称修改值会影响另一个名称看到的内容,而替换一个变量不会替换另一个名称所引用的对象。

两个 Python 字典变量指向同一个字典

浅层复制字典

内置的 dict() 构造函数和字典的 copy() 方法都会创建新的外层字典。更新或删除副本中的顶层条目不会影响原字典。不过,这两个字典仍引用相同的嵌套列表、字典或其他可变值,因此通过任一名称修改这些值,都可能在另一个名称所指的字典中体现出来。

在下面的示例中,by_constructor 和 by_method 各自拥有独立的外层字典,但它们共享嵌套列表。为 name 键赋新值则只会影响所选的副本:

source = {"name": "Sherlock", "numbers": [1, 2, 3]}
by_constructor = dict(source)
by_method = source.copy()
by_constructor["name"] = "Holmes"
by_constructor["numbers"][1] = 4
print(source)
print(by_constructor)
print(by_method)
{'name': 'Sherlock', 'numbers': [1, 4, 3]}
{'name': 'Holmes', 'numbers': [1, 4, 3]}
{'name': 'Sherlock', 'numbers': [1, 4, 3]}

如果你需要一个独立的外层映射,并且可以接受嵌套值继续共享,那么这两种浅拷贝方式任选其一即可。浅拷贝不会递归复制嵌套对象。例如,为 by_constructor["name"] 赋不同的值不会改变源字典中的 name,因为这个键分别属于两个独立的外层字典。相反,修改 numbers 中的元素会体现在打印出的三个字典中,因为它们共享同一个列表。值为另一个字典时也是如此:顶层副本仍保留对该内部字典的引用。如果只需隔离某一个嵌套值,可以单独复制该值;如果需要整个嵌套结构都彼此独立,则使用深拷贝。

展示新外层字典的 Python 浅拷贝示意图

深层复制嵌套字典

如果嵌套的可变值也需要独立副本,可以使用 copy.deepcopy()。它会递归复制所包含的对象,因此下面的示例可以修改列表项和内部字典中的值,而不影响原字典。当你打算独立编辑嵌套数据时,深拷贝很有用;如果本来就希望共享嵌套值,则没有必要使用它。

import copy

source = {
    "numbers": [1, 2, 3],
    "address": {"city": "Miami"},
}
independent = copy.deepcopy(source)
independent["numbers"][1] = 4
independent["address"]["city"] = "Lexington"
print(source)
print(independent)
{'numbers': [1, 2, 3], 'address': {'city': 'Miami'}}
{'numbers': [1, 4, 3], 'address': {'city': 'Lexington'}}

如果后续修改不应在源结构和工作副本之间相互影响,深拷贝就很有用,例如创建一份嵌套设置的副本并独立修改。它会复制嵌套容器,而不只是外层字典;因此,上面的示例中列表和内部的 address 映射都保持不变。对于大型结构,深拷贝可能比浅拷贝耗费更多时间和内存,所以应根据是否需要独立的嵌套对象来选择。Python 对象可以自定义复制行为,有些资源也无法进行有意义的复制;处理这类对象时,请查阅 copy 模块文档。

实际选择取决于哪些对象需要共享:赋值会共享整个字典,浅拷贝只会分离外层字典,深拷贝还会分离嵌套对象。copy 模块文档说明了浅拷贝和深拷贝如何处理复合对象以及自定义复制行为的对象。