1. 首先来看赋值,浅拷贝,深拷贝。
  1. 一赋值:
  1. a=['word',2,3]
  2. b=a
  3. print id(a),id(b)
  4. print [id(x) for x in a]
  5. print [id(x) for x in b]
  6. a[0]='hello'
  7. print a
  8. print b
  1. E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter8.py
  1. 24709080 24709080
  1. [24410496, 21298404, 21298392]
  1. [24410496, 21298404, 21298392]
  1. ['hello', 2, 3]
  1. ['hello', 2, 3]
  1. id(x)代表的是变量的内存地址,从上面可以看到,对于赋值来说,赋值是先给一个变量或者对象分配了内存,然后将这个内存地址赋给了其他值。因此从上面的结果可以看到,ab的内存地址是一样的,而且里面的元素的内存地址也是一样的。因此在修改a[0]='hello'后,b中的元素也一起变更了
  1. 但是如果修改a的值:
  1. a='world'
  2. print id(a)
  3. print id(b)
  1. E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter8.py
  1. 25298904 25298904
  1. [25000320, 20839652, 20839640]
  1. [25000320, 20839652, 20839640]
  1. ['hello', 2, 3]
  1. ['hello', 2, 3]
  1. 25338016
  1. 25298904
  1. 我们发现a的地址却发生改变了,这是因为字符串是不可变对象。因此重新a被重新赋值的话内存就需要发生改变
  1.  
  1.  
  1. 浅拷贝:
  1. a=[3,[55,44],(7,8,9)]
  2. b=list(a)
  3. print id(a),id(b)
  4. print [id(x) for x in a]
  5. print [id(x) for x in b]
  1. E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter8.py
  1. 23742064 23280376
  1. [20315352, 23726040, 19250872]
  1. [20315352, 23726040, 19250872]
  1. 当使用list,切片,copy.copy的时候,都是属于浅拷贝。可以看到abid不一样,但是里面的元素都是一样的。因此浅拷贝只是将容器内的元素地址复制了一份。相当于新瓶装老酒。
  1. 继续来看下面的代码:
  1. a[1].append(33)
  2. print a
  3. print b
  1. E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter8.py
  1. [3, [55, 44, 33], (7, 8, 9)]
  1. [3, [55, 44, 33], (7, 8, 9)]
  1. 由于list是可变对象,因此在添加的时候无需另外分配地址,因此b中也会跟着进行更新
  1. 我们来对元组进行更新
  1. a[2]+=(10,11)
  2. print a
  3. print b
  4. print [id(x) for x in a]
  5. print [id(x) for x in b]
  1. E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter8.py
  1. [3, [55, 44], (7, 8, 9, 10, 11)]
  1. [3, [55, 44], (7, 8, 9)]
  1. [21298392, 24709080, 20021184]
  1. [21298392, 24709080, 20037304]
  1. 从结果可以看到a中进行了更新。但是b中并没有更新。从结果中可以看到a中的元组元素地址已经发生了改变。由于元组是不可变对象,因此重新赋值的时候相当于重新分配了个地址。我们在http://www.pythontutor.com/来看下每步的结果,这样更直观。

  1. 可以看到b是另外一个容器,但是容器内的元素都是指向a中元素的地址

  1. 对于a中的元组修改后,元组指向另外一个地址。但是b中的元素仍然指向之前的地址

  1. 从这个图中可以很形象的看到浅拷贝中的内存指向。
  1.  
  1. 深拷贝
  1. a=['world',[1,2,3]]
  2. b=deepcopy(a)
  3. print id(a),id(b)
  4. print [id(x) for x in a]
  5. print [id(y) for y in b]
  1. E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter8.py
  1. 24004368 24019752
  1. [23690176, 24005568]
  1. [23690264, 24019712]
  1. 采用深拷贝后,发现无论是ab还是a,b中的元素,内存地址都不一样了
  1. a[1].append(4)
  2. print a
  3. print b
  1. E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter8.py ['world', [1, 2, 3, 4]]
  1. ['world', [1, 2, 3]]
  1. 修改a中的可变对象,b中也未同步修改。更证明了ba是相互独立的两个对象。
  1. 第一步,ab无论是容器还是容器中的元素都是不同的地址

所以即使a中修改了容器中元素的值,b中也没有进行修改

  1. 这里总结一下:

1. 赋值是将一个对象的地址赋值给一个变量,让变量指向该地址( 旧瓶装旧酒 )。

2. 浅拷贝是在另一块地址中创建一个新的变量或容器,但是容器内的元素的地址均是源对象的元素的地址的拷贝。也就是说新的容器中指向了旧的元素( 新瓶装旧酒 )。

3. 深拷贝是在另一块地址中创建一个新的变量或容器,同时容器内的元素的地址也是新开辟的,仅仅是值相同而已,是完全的副本。也就是说( 新瓶装新酒 )。

  1.  
  1. 函数的参数:
  1. 我们首先来看一个函数的调用
  1. def f(a,b):
  2.     a+=b
  3.     return a
  4.  
  5. if __name__ == "__main__":
  6.     x=1
  7.     y=2
  8.     ret=f(x,y)
  9.     print x,ret
  1. E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter8.py
  1. 1 3
  1. f函数中传入x,y,经过f运算后,返回的是3,但是x的值并没有发生改变。我们将代码改下,将参数变成列表。
  1. def f(a,b):
  2.     a+=b
  3.     return a
  4.  
  5. if __name__ == "__main__":
  6.     x=[1,2]
  7.     y=[3,4]
  8.     ret=f(x,y)
  9.     print x,ret
  1. E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter8.py
  1. [1, 2, 3, 4] [1, 2, 3, 4]
  1. 返回的结果是[1,2,3,4],但同时x的值也发生了变化,变成了[1,2,3,4]。为什么在第一次的时候x的值并没有发生变化,但是在第二次时候发生了变化。这里涉及到了函数参数传递的两种方法:值传递和引用传递
  1. 值传递:被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值
  1. 引用传递:被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
  1.  
  1. 简单点说,值传递是会对被赋值的参数另外开辟一个内存空间,修改参数的时候修改的是局部变量的内存空间,但是引用传递是直接修改传入参数的内存空间
  1.  
  1. 我们将代码修改下:
  1. def f(a,b):
  2.     print 'before used:%d' % id(a)        
  3.     a+=b
  4.     print 'in the function:%d' % id(a)   
  5.     return a
  6. if __name__ == "__main__":
  7.     x=1
  8.     y=2
  9.     print 'before funciton:%d' % id(x)    
  10.     ret=f(x,y)
  11.     print x,ret
  1. E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter8.py
  1. before funciton:22752576
  1. before used:22752576
  1. in the function:22752552
  1. 1 3
  1. 通过在代码里面新增id函数,可以看到在第一步和第二步中的内存地址是一样的,但是经过a+=b后第三步的地址发生了变化。可以看出在传入参数的时候,是将x的地址传入进去,所以ax的地址是一样的。经过运算后a变成了3并且重新申请了内存,因此a指向了内存22752552,而x仍然是指向内存22752576
  1. 那我们来看下列表的呢:
  1. def f(a,b):
  2.     print 'before used:%d' % id(a)
  3.     a+=b
  4.     print 'in the function:%d' % id(a)
  5.     return a
  6. if __name__ == "__main__":
  7.     x=[1,2]
  8.     y=[3,4]
  9.     print 'before funciton:%d' % id(x)
  10.     ret=f(x,y)
  11.     print x,ret
  1. E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter8.py
  1. before funciton:25410576
  1. before used:25410576
  1. in the function:25410576
  1. [1, 2, 3, 4] [1, 2, 3, 4]
  1. 从上面看到,在传入的开始和a+=b后,a的地址是始终和x的地址空间一样。因此a始终指向x的内存空间。这就是为什么在经过f(x,y)后x的值发生了变化,原因就在于由于a指向了x的内存空间。因此x的内存的值被发生了改变
  1. 下面的流程图可以看到,在函数内部,a,b都指向了x,y的内存

  1. 在经过计算后的ret值以及a都指向x的内存空间

  1. 总结一下:python不允许程序员选择采用传值还是传引用。Python参数传递采用的肯定是“传对象引用”的方式。这种方式相当于传值和传引用的一种综合。如果函数收到的是一个可变对象(比如字典或者列表)的引用,就能修改对象的原始值--相当于通过“传引用”来传递对象。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能直接修改原始对象--相当于通过“传值'来传递对象。
  1.  
  1. 我们来看下由于传值导致的问题。书中列举了一个幽灵校车的例子:
  1. class HauntedBus:
  2.     def __init__(self,passenagers=[]):
  3.         self.passengers=passenagers
  4.     def pick(self,name):
  5.         self.passengers.append(name)
  6.     def drop(self,name):
  7.         self.passengers.remove(name)
  8.  
  9. if __name__ == "__main__":
  10.     bus1=HauntedBus(['Alice','Bill'])
  11.     print 'bus1 passenger: %s' % bus1.passengers
  12.     bus1.pick('Charlie')
  13.     bus1.drop('Alice')
  14.     print 'bus1 passenger: %s' % bus1.passengers
  15.     bus2=HauntedBus()
  16.     bus2.pick('Carrie')
  17.     print 'bus2 passenger: %s' % bus2.passengers
  18.     bus3=HauntedBus()
  19.     print 'bus3 passenger: %s' % bus3.passengers
  20.     bus3.pick('Dave')
  21.     print 'bus2 passenger: %s' % bus2.passengers
  1. E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter8.py
  1. bus1 passenger: ['Alice', 'Bill']
  1. bus1 passenger: ['Bill', 'Charlie']
  1. bus2 passenger: ['Carrie']
  1. bus3 passenger: ['Carrie']
  1. bus2 passenger: ['Carrie', 'Dave']
  1.  
  1. 从结果来看,bus1没有问题,首先在车上的是AliceBill,然后接上了CharlieAlice下车,最后车上只剩下了BillCharlie
  1.  
  1. 但是bus2bus3就很诡异了。首先bus2bus3在实例化的时候都没有传入初始值。首先bus2接到了Carrie,但是在bus3实例化后打印bus3上的乘客,居然有Carrie,然后bus3接到了Dave,此时再看bus2上的乘客,居然Dave也在bus2上。这里bus3bus2共享了一个乘客列表。导致出现了一堆幽灵学生。为什么会产生这样的现象呢。我们打印下每次passenger的内存空间
  1. class HauntedBus:
  2.     def __init__(self,passenagers=[]):
  3.         print 'passenagers %d' % id(passenagers)
  4.         self.passengers=passenagers
  5.         print 'self.passengers %d' % id(self.passengers)
  6.     def pick(self,name):
  7.         self.passengers.append(name)
  8.     def drop(self,name):
  9.         self.passengers.remove(name)
  10.  
  11. if __name__ == "__main__":
  12.     bus2=HauntedBus()
  13.     bus2.pick('Carrie')
  14.     print 'bus2 passenger: %s' % bus2.passengers
  15.     bus3=HauntedBus()
  16.     print 'bus3 passenger: %s' % bus3.passengers
  17.     bus3.pick('Dave')
  18.     print 'bus2 passenger: %s' % bus2.passengers
  1. E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter8.py
  1. passenagers 24903512
  1. self.passengers 24903512
  1. bus2 passenger: ['Carrie']
  1. passenagers 24903512
  1. self.passengers 24903512
  1. bus3 passenger: ['Carrie']
  1. bus2 passenger: ['Carrie', 'Dave']
  1. 从结果可以看到bus2bus3中的passengers都是引用的同一个内存地址。因为passengers被初始化成了[]。而且list是可变对象,因此bus3bus2在引用时候都是引用的[]。从而导致出现了幽灵学生。下面的结果可以看到bus2bus3passenagers都是同一个对象

  1. 那么如何避免这种现象呢,只需要在初始化的时候将passengers赋值为None,然后对None进行判断
  1. class HauntedBus:
  2.     def __init__(self,passenagers=None):
  3.         if passenagers is None:
  4.             self.passengers=[]
  5.         else:
  6.             self.passengers=passenagers
  7.         print 'passenagers %d' % id(passenagers)
  8.         print 'self.passengers %d' % id(self.passengers)
  9.     def pick(self,name):
  10.         self.passengers.append(name)
  11.     def drop(self,name):
  12.         self.passengers.remove(name)
  13. if __name__ == "__main__":
  14.     bus2=HauntedBus()
  15.     bus2.pick('Carrie')
  16.     print 'bus2 passenger: %s' % bus2.passengers
  17.     bus3=HauntedBus()
  18.     print 'bus3 passenger: %s' % bus3.passengers
  19.     bus3.pick('Dave')
  20.     print 'bus2 passenger: %s' % bus2.passengers
  1. print 'bus3 passenger: %s' % bus3.passengers
  1.  
  1. E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter8.py
  1. passenagers 1410247452
  1. self.passengers 24769240
  1. bus2 passenger: ['Carrie']
  1. passenagers 1410247452
  1. self.passengers 24772400
  1. bus3 passenger: []
  1. bus2 passenger: ['Carrie']
  1. bus3 passenger: ['Dave']
  1. 从这可以看到幽灵学生的现象消失了,从self.passengers的每次地址来看,bus2bus3是不同的地址空间。下面的结果运行也可以看到bus2bus3passengers的地址空间是不一样的

  1.  
  1.  
  1.  
  1.  
  1.  
  1.  
  1.  
  1.  

流畅的python学习笔记第八章:深拷贝,浅拷贝,可变参数的更多相关文章

  1. [Python学习笔记][第八章Python异常处理结构与程序调试]

    1/30 第八章Python异常处理结构与程序调试 异常处理 try-except结构 try: try块 except Exception: except块 try-except-else结构 tr ...

  2. 流畅的python学习笔记:第三章

    字典的变种: OrderedDict 首先来看下面的代码,在一个字典中,有name,age,city,在遍历这个字典的时候.顺序却是随机的,不是按照我们添加的顺序也就是name->age-> ...

  3. 流畅的python学习笔记:第二章

    第二章开始介绍了列表这种数据结构,这个在python是经常用到的结构 列表的推导,将一个字符串编程一个列表,有下面的2种方法.其中第二种方法更简洁.可读性也比第一种要好 str='abc' strin ...

  4. 流畅的python学习笔记:第一章

    这一章中作者简要的介绍了python数据模型,主要是python的一些特殊方法.比如__len__, __getitem__. 并用一个纸牌的程序来讲解了这些方法 首先介绍下Tuple和nametup ...

  5. 流畅的python学习笔记:第五章

    在python中一切都可以视作为对象,包括函数.我们来看个例子: def function_try():     '''it is funciton try doc'''     print 'fun ...

  6. 流畅的python学习笔记第七章:装饰器

    装饰器就如名字一样,对某样事物进行装饰过后然后返回一个新的事物.就好比一个毛坯房,经过装修后,变成了精装房,但是房子还是同样的房子,但是模样变了. 我们首先来看一个函数.加入我要求出函数的运行时间.一 ...

  7. 闭包(流畅的python 学习笔记)

    什么是闭包 其实,闭包指延伸了作用域的函数,其中包含函数定义体中引用.但是不在定义体中定义的非全局变量.函数是不是匿名的没有关系,关键是它能访问定义体之外定义的非全局变量. 示例 7-8 averag ...

  8. 流畅的python学习笔记:第九章:符合python风格的对象

    首先来看下对象的表现形式: class People():     def __init__(self,name,age):         self.name=name         self.a ...

  9. 流畅的python学习笔记:第十三章:重载运算符__add__,__iadd__,__radd__,__mul__,__rmul__,__neg__,__eq__,__invert__,__pos__

    在前面第十章以及第一章的时候介绍了Vector对象的运算符重载.第十三章专门介绍运算符重载.这里我们看几个之前没讲过的运算符__neg__,__pos__,__invert__ class Vecto ...

随机推荐

  1. C++并发实战 与多线程

    http://blog.csdn.net/column/details/ccia.html

  2. SecureCRT介绍、安装、使用(转)

    http://blog.csdn.net/liang19890820/article/details/49701429 简介 SecureCRT是一款支持SSH(SSH1和SSH2)的终端仿真程序,简 ...

  3. 用PHP实现弹出消息提示框

    方法一: echo "<script>alert('提示内容')</script>"; 方法二: echo '<script language=&qu ...

  4. wmware下载地址

    https://my.vmware.com/cn/group/vmware/info?slug=desktop_end_user_computing/vmware_workstation/8_0 粗体 ...

  5. 【温故知新】——Bootstrap响应式知识点复习

    前言:本文是自己在学习课程中的课程笔记,这里用来温故知新的,并非本人原创. 开发工具 1.记事本,Editplus,... ... 2.Sublime,Dreamweaver 3.Webstorm = ...

  6. linux 查找并操作

    find -depth 1 -name 'aa*' | xargs tar -cvf aa.tar 这个命令将为查找当前目录下的所有已aa开头的文件,然后将所有结果"执行打包",打 ...

  7. 【Python】使用制表符换行符来添加空白

    在编程中,在打印时,有时候需要显示出来的数据看着舒服一点,那么使用制表符(\t).换行符(\n)即可轻松实现 >>> print('zhangsan')zhangsan 加入制表符后 ...

  8. -ROOT-表和.META.表结构详解

    在<HBase技术简介>中我们知道,HBase中有两个特殊的表:-ROOT-和.META.. 由于HBase中的表可能非常大,故HBase会将表按行分成多个region,然后分配到多台Re ...

  9. 利用动态图添加Loading动画

    opacity:CSS3中的属性,调节透明度,一般取值0.5 添加思想: 1.对超链接添加点击事件,通过new {@onclick="showLoading()"} Html.Ac ...

  10. Node.js 抓取电影天堂新上电影节目单及ftp链接

    代码地址如下:http://www.demodashi.com/demo/12368.html 1 概述 本实例主要使用Node.js去抓取电影的节目单,方便大家使用下载. 2 node packag ...