- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议38:使用 copy 模块深拷贝对象
在正式讨论本节内容之前我们先来了解一下浅拷贝和深拷贝的概念:
浅拷贝(shallow copy):构造一个新的复合对象并将从原对象中发现的引用插入该对象中。浅拷贝的实现方式有多种,如工厂函数、切片操作、copy模块中的copy操作等。
深拷贝(deep copy):也构造一个新的复合对象,但是遇到引用会继续递归拷贝其所指向的具体内容,也就是说它会针对引用所指向的对象继续执行拷贝,因此产生的对象不受其他引用对象操作的影响。深拷贝的实现需要依赖 copy 模块的deepcopy()操作。
下面我们通过一段简单的程序来说明浅拷贝和深拷贝之间的区别。
import copy class Pizza(object): def __init__(self,name,size,price): self.name=name self.size=size self.price=price def getPizzaInfo(self): ①获取Pizza 相关信息 return self.name,self.size,self.price def showPizzaInfo(self): ②显示Pizza 信息 print "Pizza name:"+self.name print "Pizza size:"+str(self.size) print "Pizza price:"+str(self.price) def changeSize(self,size): self.size=size def changePrice(self,price): self.price=price class Order(object): ③订单类 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 infomation:" customer1.getorderdetail() print "-------------------------------"
程序描述的是客户在Pizza店里下了一个订单,并将具体的订单信息打印出来的场景。运行输出结果如下:
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 -------------------------------
假设现在客户2也想下一个跟客户1一样的订单,只是要将预定的水果披萨的尺寸和价格进行相应的修改。于是服务员拷贝了客户1的订单信息并做了一定的修改,代码如下:
customer2=copy.copy(customer1) print "order 2 customer name:"+customer2.customername customer2.changeName("li") customer2.getPizza(2).changeSize(9) customer2.getPizza(2).changePrice(30) print "customer2 order infomation:" customer2.getorderdetail() print "-------------------------------------"
上面这段程序的输出也没有什么问题,完全满足了客户2的需求。输出结果如下所示:
order 2 customer name:zhang customer2 order infomation: 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的订单信息:
print "customer1 order information:" customer1.getorderdetail()
你会发现客户1的订单内容除了客户姓名外,其他的居然和客户2的订单具体内容一样了。
------------------------------------ customer1 order infomation: 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根本没要求修改订单的内容,这样的结果必定会直接影响到客户满意度。问题出现在哪里?这是我们本节要重点讨论的内容。我们先来分析客户1和客户2订单内容的关系图,如图4-1所示。
图4-1 客户1和客户2订单的关系示意图
customer1中的pizzaList是一个由Pizza对象组成的列表,其中存放的实际是对一个个具体Pizza对象的引用,在内存中就是一个具体的地址,可以通过查看id得到相关信息。
print id(customer1.pizzaList[0]) 输出14099440 print id(customer1.pizzaList[1]) 输出14101392 print id(customer1.pizzaList[2]) 输出 14115344 print id(customer1.pizzaList) 输出 13914800
customer2的订单通过copy.copy(customer1) 获得,通过id函数查看customer2中pizzaList的具体Pizza对象你会发现它们和customer1中的输出是一样的(读者可以自行验证)。这是由于通过copy.copy()得到的customer2是customer1的一个浅拷贝,它仅仅拷贝了pizzalist里面对象的地址而不对对应地址所指向的具体内容(即具体的pizza)进行拷贝,因此customer2中的pizzaList所指向的具体内容是和customer1中一样的,如图4-1所示。所以对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(customer1)改为copy.deepcopy()来实现(请读者自行验证)。关于对象拷贝读者可以查看网页http://en.wikipedia.org/wiki/Object_copy进行阅读扩展。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论