>>> from env_helper import info; info()
页面更新时间: 2024-03-14 20:44:38
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-18-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

4.3. 使用 copy 模块深拷贝对象

在正式讨论本节内容之前我们先来了解一下浅拷贝和深拷贝的概念:

  • 浅拷贝(shallow copy ):构造一个新的复合对象并将从原对象中发现的引用插人该对象中。浅拷贝的实现方式有多种,如工厂函数数、切片操作、copy模块中的 copy() 操作等。

  • 深拷贝(deepCoPy):也构造一个新的复合对象,但是遇到引用会继续递归拷贝其所指 向的具体内容,也就是说它会针对引用所指向的对象继续执行拷贝, 因此产生的对象不受其他引用对象操作的影响^深拷贝的实现需要依赖copy模块的 deepcopy() 操作。

下面我们通过一段简单的程序来说明浅拷贝和深拷贝之间的区别。

>>> import copy
>>> class Pizza():
>>>     def __init__ (self,name,size,price):
>>>         self.name=name
>>>         self.size=size
>>>         self.price=price
>>>     def getPizzaInfo(self):
>>>         return self.name,self.size,self.price
>>>     def showPizzaInfo(self):
>>>         print("Pizza name :"+self.name)
>>>         print("Pizza size:"+self.size)
>>>         print("Pizza price:"+self.price)
>>>     def changeSize(self,size):
>>>         self.size=size
>>>     def changePrice(self,price):
>>>         self.price=price
>>> class Order():
>>>     def __init__(self,name):
>>>         self.customername=name
>>>         self.pizzaList=[]
>>>         self.pizzaList.append(Pizza("mushroom","12","30"))
>>>     def ordermore(self,pizza):
>>>         self.pizzaList.append(pizza)
>>>     def changeName(self,name):
>>>         self.customername=name
>>>     def getorderdetail(self):
>>>         print("customer name:"+self.customername)
>>>         for i in self.pizzaList:
>>>             i.showPizzaInfo()
>>>     def getPizza(self,number):
>>>         return self.pizzaList[number]
>>> customer1=Order("zhang")
>>> customer1.ordermore(Pizza("seafood","9","40"))
>>> customer1.ordermore(Pizza("fruit","12","35"))
>>> print("customer1 order information:")
>>> customer1.getorderdetail()
customer1 order information:
customer name:zhang
Pizza name :mushroom
Pizza size:12
Pizza price:30
Pizza name :seafood
Pizza size:9
Pizza price:40
Pizza name :fruit
Pizza size:12
Pizza price:35

程序描述的是客户在Pizza店里下了一个订单,并将具体的订单信息打印出来的场景。 运行输出结果如下:

>>> customer2=copy.copy(customer1)
>>> print("order2 customer name")
>>> customer2.customername
>>> customer2.changeName("li")
>>> customer2.getPizza(2).changeSize("9")
>>> customer2.getPizza(2).changePrice("30")
>>> print("customer 2 order information:")
>>> customer2.getorderdetail()
order2 customer name
customer 2 order information:
customer name:li
Pizza name :mushroom
Pizza size:12
Pizza price:30
Pizza name :seafood
Pizza size:9
Pizza price:40
Pizza name :fruit
Pizza size:9
Pizza price:30

假设现在客户2也想下一个跟客户1 —样的订单,只是要将预定的水果披萨的尺寸和价 格进行相应的修改。于是服务员拷贝了客户]的订单信息并做了一定的修改,代码如下;

在修改完客户2的订单信息之后,现在我们再来检査一下客户1的订单信息:

>>> print("customer1 order information:")
>>> customer1.getorderdetail()
customer1 order information:
customer name:zhang
Pizza name :mushroom
Pizza size:12
Pizza price:30
Pizza name :seafood
Pizza size:9
Pizza price:40
Pizza name :fruit
Pizza size:9
Pizza price:30

你会发现客户1的订单内容除了客户姓名外,其他的居然和客户2的订单具体内容一样了。

这是怎么回事呢?客户1本没要求修改订单的内容,样的结果必定会直接影响到客 户满意度。 问题出现在哪里?这是我们本节要重点讨论的内容。我们先来分析客户1和客户 2订单内容的关系图,如图所示。

_images/38-1.png
图4-1 客户1和客户2订单的关系示意图

customerl中的pizzaList是一个由Pizza对象组成的列表,其中存放的实际是对一个个 具体Pizza对象的引用,在内存中就是一个具体的地址,可以通过査看id得到相关信息a

>>> print(id (customer1 .pizzaList [0]))
>>> print(id (customer1 .pizzaList [1]))
>>> print(id (customer1 .pizzaList [2]))
>>> print(id (customer1.pizzaList))
140156661010128
140156661011152
140156661010832
140156660989568

customer2的订单通过 copy.copy(customerl) 获得,通过 id 函数査看 customer2 中 pizzaList的具体Pizza对象你会发现它们和customerl中的输出是一样的(读者可以自行验 证)。这是由于通过 copy.copy() 得到的 customer2customer1的一个浅拷贝,它仅仅拷贝了 pizzalist里面对象的地址而不对对应地址所指向的具体内容(即具体的pizza)进行拷贝,因此 customer2 中的 pizzaList 所指向的具体内容是和 customer1 中一样的,如图所示。

所以对pizza fruit的修改直接影响了 customer1 的订单内容。实际上在包含引用的数据结构中, 浅拷贝并不能进行彻底的拷贝, 当存在列表、字典等不可变对象的时候,它仅仅拷贝其引用地址。 要解决上述问题需要用到深拷贝,深拷贝不仅拷贝引用也拷贝引用所指向的对象,因此深拷贝得到的对象和原对象是相互独立的。

上面的例子充分展示了浅拷贝和深拷贝之间的差异,在实际应用中要特別注意这两者之 间的区别。 实际上Python copy 模块提供了与浅拷贝和深拷贝对应的两种方法的实现, 通过 名字便可以轻易进行区分,模块在拷贝出现异常的时候会拋出 copy.error

copy.copy(x) : Return a shallow copy of x. copy, deepcopy (x) : Return a deep copy of x. exception copy- error : Raised for module specific errors.

实际上,上面的程序应该将 customer2=copy.copy(customer]) 改为 copy.deepcopy() 来实现。