在 Python 编程中,字典是一种非常强大的数据结构,它允许我们将键值对关联起来,并以高效的方式查找和操作这些数据。当我们尝试在字典中存储自定义对象时,通常会遇到一个关键概念:Python 中的对象赋值实际上是引用赋值,而非对象本身的深拷贝。这意味着将自定义对象放入字典时,字典中存储的是对该对象的引用,而非对象的一个全新副本。

标题:深入理解 Python 字典存储自定义对象:引用与深拷贝的重要性


存储自定义对象的基本示例

假设我们有一个简单的 Person 类:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

# 创建一个 Person 对象
p1 = Person("Alice", 30)

# 将对象存储到字典中
people_dict = {}
people_dict["alice"] = p1

在这个例子中,people_dict 字典现在包含一个键为 "alice" 的项,其值是对 Person 类型的 p1 对象的引用。如果我们修改 p1 的属性:

p1.age = 31

那么通过字典访问这个对象时,我们会发现其年龄也被更新了:

print(people_dict["alice"].age)  # 输出:31

这是因为字典中存储的并不是 Person 对象的独立副本,而是指向同一内存地址的引用。

深拷贝与浅拷贝的区别

在涉及嵌套数据结构或自定义对象时,这种引用行为可能会导致意外的结果。例如,如果自定义对象中包含可变类型的属性(如列表或另一个自定义对象),直接将这样的对象存入字典并对其进行修改,会影响到通过字典获取的对象。

class Address:
    def __init__(self, street, city):
        self.street = street
        self.city = city

class Person:
    def __init__(self, name, age, address):
        self.name = name
        self.age = age
        self.address = address

address = Address("Main St.", "Springfield")
p1 = Person("Bob", 40, address)
people_dict["bob"] = p1

# 修改原始地址对象
address.city = "Shelbyville"

# 字典中的人的地址也变了
print(people_dict["bob"].address.city)  # 输出:Shelbyville

解决办法:深拷贝

为了避免这种共享状态带来的问题,有时我们需要确保字典存储的是对象的一个完整副本,而不是引用。Python 提供了 copy 模块中的 deepcopy 函数来实现这一目标:

import copy

# 使用深拷贝存储对象
people_dict["bob_deepcopy"] = copy.deepcopy(p1)

# 此时即使修改原始地址对象,深拷贝的对象不会受影响
address.city = "Capital City"
print(people_dict["bob"].address.city)  # 输出:Capital City
print(people_dict["bob_deepcopy"].address.city)  # 输出:Shelbyville

总之,在 Python 中利用字典存储自定义对象时,务必注意默认情况下存储的是对象引用。对于那些需要保持独立状态的情况,请使用 deepcopy 进行深拷贝,以避免因共享引用而导致的意料之外的数据变化。