zoukankan      html  css  js  c++  java
  • Python 浅拷贝和深拷贝

    一、前奏:熟悉Python内存管理

    在Python中,变量在第一次赋值时自动声明,在创建---也就是赋值的时候,解释器会根据语法和右侧的操作数来决定新对象的类型。

    引用计数器:一个内部跟踪变量

    引用计数:每一个对象各有多少个引用

    当对象被创建并(将其引用)赋值给变量时,该对象的引用计数就被设置为 1

    >>> x = 3.14

    语句 x=3.14,创建一个浮点型对象并将其引用赋值给了x,x是第一个引用,该对象的引用计数为1

    当一个对象(的引用)又被赋值到其他变量,或做参数传递等,该对象的一个新的引用(或叫别名)被创建,则该对象的引用计数自动+1。

    以下都会增加引用计数:

    y = x   #做别名
    foo(x)  #做参数传递
    mylis = [1,2,x,'a'] #成为容器对象的一个元素

    以下都会减少引用计数:

    复制代码
    del x   #del显式销毁
    
    bar = x 
    x = True    #对象的一个别名被赋值给其他对象
    
    mylis.remove(x) #对象被从窗口对象中移除
    
    del mylis   #窗口对象本身被销毁
    复制代码

    二、Python的复制

    从上面可见,对象的赋值实际上是对象的引用。当创建一个对象,然后把它赋给另一个变量的时候,python并没有拷贝这个对象,而只是拷贝了这个对象的引用。

    当你对一个对象赋值的时候(做为参数传递,或者做为返回值),Python和Java一样,总是传递原始对象的引用,而不是一个副本。

    复制代码
    """传递原始对象的引用,而不是一个副本"""
    a = [1,2,3]
    b = a
    b.append(100)
    print b         #[1, 2, 3, 100]
    print a         #[1, 2, 3, 100]
    print id(a)     #11530368
    print id(b)     #11530368 
    复制代码

    如 果你想修改一个对象,而且想让原始的对象不受影响,那你就需要对象复制。

    可以 使用copy.copy(),它可以进行对象的浅复制(shallow copy),它复制了对象,但对于对象中的元素,依然使用引用.

    (1)、使用切片[:]操作进行拷贝

    (2)、使用工厂函数(如list/dir/set)等进行拷贝

    (3)、copy.copy()

    复制代码
    >>> jack = ['jack',['age',20]]
    >>> tom = jack[:]
    >>> anny = list(jack)
    >>> jack
    ['jack', ['age', 20]]
    >>> tom
    ['jack', ['age', 20]]
    >>> anny
    ['jack', ['age', 20]]
    >>> print id(jack),id(tom),id(anny)
    13457088 18487376 18489136
    复制代码

    接下来修改上面例子,对姓名和年级进行修改:

    复制代码
    >>> tom[0]='tom'
    >>> anny[0]='anny'
    >>> print tom
    ['tom', ['age', 20]]
    >>> print anny
    ['anny', ['age', 20]]
    >>> anny[1][1]
    20
    >>> anny[1][1]= 18
    >>> anny[1][1]
    18
    >>> print jack,tom,anny
    ['jack', ['age', 18]] ['tom', ['age', 18]] ['anny', ['age', 18]]
    复制代码

    发现,虽然姓名都对号了,但是年龄却都变成了18.这是为什么呢?

    我们看看它们元素的id

    复制代码
    >>> [id(x) for x in jack]
    [13463040, 13456608]
    >>> [id(x) for x in tom]
    [13463424, 13456608]
    >>> [id(x) for x in anny]
    [18501664, 13456608]
    复制代码

    发现,其中列表中  姓名字符串  id都不一样,但是 年龄列表id却都相同。

    这是因为:python中字符串不可以修改,所以在为tom和anny重新命名的时候,会重新创建一个’tom’和’anny’对象,替换旧的’jack’对象。

    这就说明了,浅复制(shallow copy),它复制了对象,但对于对象中的元素,依然使用引用.

    复制代码
    """浅copy"""
    import copy
    aa = [1,2,3]
    bb = copy.copy(aa)
    print id(aa)    #11533088
    print id(bb)    #12014776
    bb[0] =100
    print bb        #[100, 2, 3]
    print aa        #[1,2,3]
    #由于数字不可变,修改的时候会替换旧的对象
    print [id(x) for x in bb]   #[10247196, 10246388, 10246376]
    print [id(y) for y in aa]   #[10246400, 10246388, 10246376]
    复制代码

    下面试试对象中可变元素:

    复制代码
    lis = [['a'],[1,2],['z',23]]
    copyLis = copy.copy(lis)
    copyLis[1].append('bar')
    print copyLis   #[['a'], [1, 2, 'bar'], ['z', 23]]
    print lis       #[['a'], [1, 2, 'bar'], ['z', 23]]
    复制代码

    如果希望复制一个容器对象,以及它里面的所有元素(包含元素的子元素),使用copy.deepcopy,这个方法会消耗一些时间和空间,不过,如果你需要完全复制,这是唯一的方法.

    复制代码
    """深copy"""
    deepLis = copy.deepcopy(lis)
    deepLis[1].append('foo')    
    print deepLis   #[['a'], [1, 2,'foo'], ['z', 23]]
    print lis       #[['a'], [1, 2], ['z', 23]]
    复制代码


    注意:

    1、对于非容器类型(如数字、字符串、和其他‘原子’类型的对象)没有被拷贝一说。

    2、如果元祖变量只包含原子类型对象,则不能深copy。

    3、对于python比较熟悉的人们都应该了解这个事实,在python中,strings, tuples, 和numbers是不可更改的对象,而list,dict等则是可以修改的对象。

    4、我们刚才描述的浅拷贝和深拷贝操作都可以在copy模块中找到。其实copy模块中只有两个函数可用:copy()进行浅拷贝操作,而deepcopy()进行深拷贝操作。

  • 相关阅读:
    bzoj1415 NOI2005聪聪和可可
    Tyvj1952 Easy
    poj2096 Collecting Bugs
    COGS 1489玩纸牌
    COGS1487 麻球繁衍
    cf 261B.Maxim and Restaurant
    cf 223B.Two Strings
    cf 609E.Minimum spanning tree for each edge
    cf 187B.AlgoRace
    cf 760B.Frodo and pillows
  • 原文地址:https://www.cnblogs.com/edisonxiang/p/4608415.html
Copyright © 2011-2022 走看看