python学习, 从copy和deepcopy开始的一些浅析

前言/可以跳过的内容:本文的起因简单的概括起来就是周六晚上,因为宿舍哥们的呼噜声实在是震撼让人睡不着,无奈的想用看书的方式催眠自己,但打开Python Cookbook后,本青年就陷到这个问题。


首先,感谢
http://www.jb51.net/article/15714.htm, http://www.cnblogs.com/wait123/archive/2011/10/10/2206580.html, http://www.01happy.com/python-shallow-copy-and-deep-copy/ 的作者们,虽然我不一定有完全阅读他们写的内容,但是多少还是受到了启发的。


下面的内容,可能有些地方的描述不够专业,不够规范,不够科学。希望有看官能够指正,指导。


#0, 变量--引用--对象。
在《基础教程》的C/C++扩展的章节里,作者在介绍垃圾回收策略时,介绍了引用数的概念,在这里,差不多是再次强调了python里的 “变量--引用--对象” 这一概念。书上的这一块内容很有意思,值得多思考回味一下。我个人认为这一概念比较重要,但由于语言能力的有限,所以暂时无法说明缘由。 例如 x = [1,2,3],运行期间,x是变量,[1,2,3]是对象,这样的赋值,只是将变量x绑定到那个对象。如果有 y = x,即通过x,将y绑定到同一个对象,那么对于语句 y is x 和 y == x,都将返回True。而如果有 z = [1,2,3],那么将只有 z == x 返回True,而 z is x 将返回False,其原因是 [1,2,3] (被x,y绑定的)与[1,2,3](被z绑定的)是两个对象,虽然内容看上去相同,但实际上确实两个不同的对象,所以自然 is 会返回False。如果用内建函数id()的话,如id(x),id(z),就可以看出x与z确实是不同,既然这里说到了内建函数 id(),那就顺便也说一下。


#1,内建函数 id()
关于id()可以看这里http://docs.python.org/2/library/functions.html#id,因为之前和宿舍哥们讨论sizeof和内存的一些问题,所以对于id(),我主要关注的是“CPython implementation detail: This is the address of the object in memory.”这句话。 另外,id(a)==id(b)与a is b的关联,我没有去考虑。做一些简单的测试的话,可以发现id(a)!=id(b)的话,a is b会返回False。接下来再说一些其他的东西,这些如果用百度搜索"python id()"的话,应该会出现。那就是:
【以下的内容是在没有查看相关参考手册的情况下写出来的,所以可能是在说废话,但基本上都是基于一些小的测试结果的,所以希望能尽量避免产生谬论的情况】
    #1.1,if x=1,y=1,then id(x)==id(y),but if x=1.1, y=1.1, then id(x)!=id(y).有说法说这是python的一种管理的小技巧,将一些常用的integer常量特别对待了,具体我没有去查明,简单接受了一下。但是像 x=1.1,这样的非整形的数值常量就没这么“特殊”了。而对于这个特例允许的整形常量的取值范围,我做了个小实验,在ubuntu-12.04/32/python2.7 下,用for循环可以测试出有效取值在[-5,256]这个闭区间上。
    #1.2,上例中x,y为字符,字符串的情况。
        #1.2.1 因为字符串是不可变的,所以用for循环来测试字符串在多长的情况下,还会有x is y的情况显然不再合适了,所以我没有去测试像 x="aaa...aa"(共n个),y="aaa...aa"(共n个),x is y为True的情况,但可以相信,这个n可以足够大。
         #1.2.2 字符的问题。对于 x,y都取相同的单个字符的情况,x is y为True;但当x,y为字符串时,便会出现字符问题,在#1.2.1的前提下,我的测试的结果显示,如果两个内容相同的字符串所包含的的字符只有数字和字母的话,那么在足够长的范围内,x is y为True;但如果包含"~","!","@"这样的特殊字符,那么即使再短的字符串,如x="!a",y="!a",x is y将为False,这里我只测试了这几个字符,希望起到抛砖引玉的作用,或者有高人能给予指点。
     最后,这一小部分的内容,是基于python的一个内存管理小策略的,因为懒,所以具体的细节我没有去探索。


#2,可变与不可变
很遗憾,因为本人懒于查阅,也懒得动手做足够的实践,所以这块内容可能探讨的比较虚。
python里,数值,字符串,元组,字典是不可变的!那么这个不可变的是什么,如果没有理解错的话,不可变的应该是在运行期间,存储在内存中的对象。联系一下#0的内容,用id()试试看就会发现这一点,如x=0,在x+=1的前后,id(x)返回的结果不同,对于字符串应该更容易理解,如x="aaa",在字符串拼接的前后,id(x)的返回结果不同。
在Python Cookbook的4.1节里有:“赋值使用的是引用...类似于a=[]这样的语句,是对名字a做了重新绑定,但却不会影响原先绑定的a的对象...只有当需要修改某些对象的时候,引用和拷贝才有可能成为问题。”,这里,我们只看省略号之间的内容。当我们尝试修改不可变的对象的内容的时候,例如修改数值,字符串拼接等,从id()的返回结果来看,虽然我们的变量名没有变,但是变量绑定的对象变了。所以对于不可变的修改的结果应该是结合拷贝的开辟新空间的处理结果。不可变对象没有变,变的是绑定。


#3,copy 与 deepcopy
终于写到这里了,虽然兜了个大圈子,但希望没有白费功夫,所述内容能被理解。
一开始刚接触到copy模块的这两个方法的时候,其实压根没怎么明白。对于所谓的“copy(浅拷贝)生成了一个新对象,该对象的内容和属性仍然引用原对象,而deepcopy(深拷贝)则从原对象真正的“复制”了一个全新的对象”的概念完全没想法。对于上面给出的链接的例子和自己实践的一些结果也不是很能理解。
在经过上面这些内容的探索之后,勉强有了点想法,现在以一个列表的例子来尽力说明。为什么不是别的,而是列表?因为列表是可变的,如果能理解#2的内容,那么不难发现用其他的现有类型来说明问题是没什么意义的;而如果用自定义类的话,我担心因为我对类的掌握不够深,而产生副作用,所以列表无疑是最好的选择。
如果有:
    x = ["str", [1,[2,3]], ['a','b']] // "str"作为数值,字符串,元组,字典这些不可变的代表,如果有
                                      // 关于这四种类型在 不可变这一点上的区别的话,则应该添加测试
                                      // 用例。
    y = copy.copy(x)
    z = copy.deepcopy(x)
    alist = [x, y, z]
    for m in alist:
       id(m[0]), id(m[1]), id(m[2])
输出的结果是:x[0]==y[0]==z[0], x[1]==y[1]!=z[1], x[2]==y[2]!=z[2],y是浅拷贝,所以y的内容都是x内容的引用的拷贝,所以id()的结果一致,对于z的结果,可以理解为:因为x[0]不可变,所以在拷贝的时候,利用了python的内存管理小策略,z[0]产生了一个相同的引用,当对z[0]做出改变的时候,就有可能出现id(z[0])!=id(x[0]),而对于z[1],z[2],因为x[1],x[2]是可变的,所以深拷贝的时候,完全产生了新的对象,注意这里的 x[n]这样的索引引用都只是绑定到内存中对象的引用。
如果进一步的:
    for m in alist:
       id(m[1][0]), id(m[1][1])
会发现对于m[1][0],x,y,z表现的结果都是一样的,而z则在m[1][1]上的表现与x,y不一样。在理解内存管理小策略的基础上,如果加上变量内容的内容也可以是变量的话,那么就应该可以理解了。
现在做点改变:
    (1)x[0] = "str0", (2)x[2].append('c')
    对于(1),因为字符串是不可变的,所以当x[0]“发生变化”时,实际上是x[0]被绑定到了新的对象,而y[0],z[0]仍然维持对原对象的引用,所以id()没有变化。
    对于(2),将看到id()的结果没有变化,但内容上,x和y都添加了新的字符,而z没有变化,因为z[2]在深复制的时候已经绑定到了新生成的一个对象上了。
   以上,就是copy和deepcopy的内容。


就这么多内容了。心情好的时候再补充吧。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值