Python的赋值与复制

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python的赋值与复制相关的知识,希望对你有一定的参考价值。

参考技术A 对于Python的初学者,在对象的使用过程中,由于对变量的赋值和对象的复制中的概念模糊,导致程序出错。

例如,下面的代码:

输出结果为:

a = [6,2,3,4,5],
b = [6,2,3,4,5],
c = [1,2,3,4,5]
a等于b?True
a等于c?True
a是b?True
a是c? False

可以看到,a,b, c所指向的对象的值都相同(a==b为True). a和b都是代表同一个对象(a is b为True)。当我们通过变量b对该列表进行修改时,由于a也指向该列表,所以当打印a,b时,我们得到相同的值。 而a和c则是代表不同的对象(a is c为False),所以修改b所指向得列表不会改变c梭子乡的列表的值.

在Python中,所有的变量都代表了对象,即便是简单的数字类型(int, float, bool),也是以对象的形式存在的。我们看下面的代码:

输出结果是:

a==b为True
a is b为True

可见,a, b都是指向同一个对象。接下来,进行下面的操作,

输出结果是:

a = 1, b = 2
a is b为False

与前面的列表不同,当我们对b做修改时,实际上是给b赋予了一个新生成的对象,对数值类型来说,所有的数值运算都会创建一个数值对象,并将这个对象指定给变量。因此,a与b指向了不同的对象,数值也不同。

再回过头来看列表对象,

我们知道,b是与a指向同一对象的变量,使用b对该对象进行修改,与使用a对该对象进行修改,效果是完全一样的。如果我们需要需要一个与a完全相同又与a相互独立的列表,那么就需要复制这个对象,也就是新建一个内容和源对象相同的对象。

对于列表来说,最简单的复制方法是通过下标索引的方式创建新的列表:

对于各种数据类型通用的对象拷贝复制,我们可以使用python内建的copy模块。

对于复杂对象(如嵌套列表)的复制,则需要注意区分浅拷贝和深拷贝。我们来看下面的代码:

得到的结果是:
a[0] is b[0]为 True
a[0] is c[0]为 False
a = [[-1, 2, 3], [4, 5, 6]]
b = [[-1, 2, 3], [7, 8, 9]]
c = [[1, 2, 3], [4, 5, 6]]
a[1] is b[1]为False

从上面的代码我们可以看到,copy函数为浅拷贝,只拷贝了对象的外层,而对象内部所包含的对象仍然指向原有的对象。而deepcopy则为深拷贝,对象内部的对象也进行了复制。

以上我们对变量的赋值和对象的复制做了更加深入的分析。在具体的使用中,我们需要根据具体来决定使用赋值、浅拷贝、深拷贝。

Python---copy()deepcopy()与赋值的区别

deepcopy之间拷贝是整个值的全部,当原来的值改变时,deepcopy不会变化

数组类型拷贝,则拷贝的值同样也会发生变化,如果是a = [1,3]这种全赋值,这原来的拷贝值不会发生变化,如果将数组的每一个位置的值赋新的值(如a[1 ] = 1,a[2] = 3)这样的话

  copy()与deepcopy()之间的主要区别是python对数据的存储方式。

首先直接上结论:

—–深复制,即将被复制对象完全再复制一遍作为独立的新个体单独存在。所以改变原有被复制对象不会对已经复制出来的新对象产生影响。 

—–而等于赋值,并不会产生一个独立的对象单独存在,他只是将原有的数据块打上一个新标签,所以当其中一个标签被改变的时候,数据块就会发生变化,另一个标签也会随之改变。

—–而浅复制要分两种情况进行讨论:

1)当浅复制的值是不可变对象(数值,字符串,元组)时和“等于赋值”的情况一样,对象的id值与浅复制原来的值相同。

2)当浅复制的值是可变对象(列表和元组)时会产生一个“不是那么独立的对象”存在。有两种情况:

第一种情况:复制的 对象中无 复杂 子对象,原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的id值与浅复制原来的值不同。

第二种情况:复制的对象中有 复杂 子对象 (例如列表中的一个子元素是一个列表),如果不改变其中复杂子对象,浅复制的值改变并不会影响原来的值。 但是改变原来的值 中的复杂子对象的值  会影响浅复制的值。

对于简单的 object,例如不可变对象(数值,字符串,元组),用 shallow copy 和 deep copy 没区别

复杂的 object, 如 list 中套着 list 的情况,shallow copy 中的 子list,并未从原 object 真的「独立」出来。也就是说,如果你改变原 object 的子 list 中的一个元素,你的 copy 就会跟着一起变。这跟我们直觉上对「复制」的理解不同。

当浅复制的值是不可变对象(数值,字符串,元组)时,代码如下:

  1.  
    >>> a="1234567"
  2.  
    >>> b=a
  3.  
    >>> id(a)
  4.  
    4367619440
  5.  
    >>> id(b)
  6.  
    4367619440
  7.  
    >>> c=copy.copy(a)
  8.  
    >>> id(c)
  9.  
    4367619440
  10.  
    >>> d=copy.deepcopy(a)
  11.  
    >>> id(d)
  12.  
    4367619440

 

当浅复制的值是可变对象(列表,字典)时,改变的值不是 复杂子对象 代码如下:

  1.  
    >>> l1=[1,2,3]
  2.  
    >>> l2=l1
  3.  
    >>> l3=copy.copy(l1)
  4.  
    >>> id(l1)
  5.  
    4367654664
  6.  
    >>> id(l2)
  7.  
    4367654664
  8.  
    >>> id(l3)
  9.  
    4367628616
  10.  
    >>> l1.append(55)
  11.  
    >>> print(l1)
  12.  
    [1, 2, 3, 55]
  13.  
    >>> print(l3)
  14.  
    [1, 2, 3]
  15.  
     

当浅复制的值是可变对象(列表,字典)时,改变的值是 复杂子对象 代码如下:

  1.  
    >>> import copy
  2.  
    >>> list1=[1,2,[‘a‘,‘b‘]]
  3.  
    >>> list2=list1
  4.  
    >>> list3=copy.copy(list2)
  5.  
    >>> list4=copy.deepcopy(list3)
  6.  
    >>> id(list1)
  7.  
    4338414656
  8.  
    >>> id(list2)
  9.  
    4338414656
  10.  
    >>> id(list3)
  11.  
    4338415016
  12.  
    >>> id(list4)
  13.  
    4338414368
  14.  
    >>> list1[2].append(‘a‘)
  15.  
    >>> id(list1)
  16.  
    4338414656
  17.  
    >>> print list1
  18.  
    [1, 2, [‘a‘, ‘b‘, ‘a‘]]
  19.  
    >>> print list3
  20.  
    [1, 2, [‘a‘, ‘b‘, ‘a‘]]
  21.  
    >>> print list4
  22.  
    [1, 2, [‘a‘, ‘b‘]]
  23.  
    >>> list1.append(33)
  24.  
    >>> id(list1)
  25.  
    4338414656
  26.  
    >>> id(list3)
  27.  
    4338415016
  28.  
    >>> print list1
  29.  
    [1, 2, [‘a‘, ‘b‘, ‘a‘], 33]
  30.  
    >>> print list3
  31.  
    [1, 2, [‘a‘, ‘b‘, ‘a‘]]


代码说明:当改变 复杂子对象中的元素时,浅复制值发生了变化; 当改变的值不是复杂子对象,浅复制的值没有发生变化。因为 浅复制 ,复杂子对象的保存方式是 作为 引用 方式存储的,所以修改 浅复制的值 和原来的值都可以 改变 复杂子对象的值。

 

python的数据存储方式

Python 存储变量的方法跟其他 OOP 语言不同。它与其说是把值赋给变量,不如说是给变量建立了一个到具体值的 reference。

当在 Python 中 a = something 应该理解为给 something 贴上了一个标签 a。当再赋值给 a 的时候,就好象把 a 这个标签从原来的 something 上拿下来,贴到其他对象上,建立新的 reference。 这就解释了一些 Python 中可能遇到的诡异情况:

  1.  
    >> a = [1, 2, 3]
  2.  
    >>> b = a
  3.  
    >>> a = [4, 5, 6] //赋新的值给 a
  4.  
    >>> a
  5.  
    [4, 5, 6]
  6.  
    >>> b
  7.  
    [1, 2, 3]
  8.  
    # a 的值改变后,b 并没有随着 a
  9.  
     
  10.  
    >>> a = [1, 2, 3]
  11.  
    >>> b = a
  12.  
    >>> a[0], a[1], a[2] = 4, 5, 6 //改变原来 list 中的元素
  13.  
    >>> a
  14.  
    [4, 5, 6]
  15.  
    >>> b
  16.  
    [4, 5, 6]
  17.  
    # a 的值改变后,b 随着 a 变了

上面两段代码中,a 的值都发生了变化。区别在于,第一段代码中是直接赋给了 a 新的值(从 [1, 2, 3] 变为 [4, 5, 6]);而第二段则是把 list 中每个元素分别改变。而对 b 的影响则是不同的,一个没有让 b 的值发生改变,另一个变了。怎么用上边的道理来解释这个诡异的不同呢?首次把 [1, 2, 3] 看成一个物品。a = [1, 2, 3] 就相当于给这个物品上贴上 a 这个标签。而 b = a 就是给这个物品又贴上了一个 b 的标签。 

技术分享图片 
第一种情况:a = [4, 5, 6] 就相当于把 a 标签从 [1 ,2, 3] 上撕下来,贴到了 [4, 5, 6] 上。在这个过程中,[1, 2, 3] 这个物品并没有消失。 b 自始至终都好好的贴在 [1, 2, 3] 上,既然这个 reference 也没有改变过。 b 的值自然不变。

技术分享图片 

第二种情况:a[0], a[1], a[2] = 4, 5, 6 则是直接改变了 [1, 2, 3] 这个物品本身。把它内部的每一部分都重新改装了一下。内部改装完毕后,[1, 2, 3] 本身变成了 [4, 5, 6]。而在此过程当中,a 和 b 都没有动,他们还贴在那个物品上。因此自然 a b 的值都变成了 [4, 5, 6]。

搞明白这个之后就要问了,对于一个复杂对象的浅copy,在copy的时候到底发生了什么? 
再看一段代码:

  1.  
    >>> import copy
  2.  
    >>> origin = [1, 2, [3, 4]]
  3.  
    #origin 里边有三个元素:1, 2,[3, 4]
  4.  
    >>> cop1 = copy.copy(origin)
  5.  
    >>> cop2 = copy.deepcopy(origin)
  6.  
    >>> cop1 == cop2
  7.  
    True
  8.  
    >>> cop1 is cop2
  9.  
    False
  10.  
    #cop1 和 cop2 看上去相同,但已不再是同一个object
  11.  
    >>> origin[2][0] = "hey!"
  12.  
    >>> origin
  13.  
    [1, 2, [‘hey!‘, 4]]
  14.  
    >>> cop1
  15.  
    [1, 2, [‘hey!‘, 4]]
  16.  
    >>> cop2
  17.  
    [1, 2, [3, 4]]
  18.  
    #把origin内的子list [3, 4] 改掉了一个元素,观察 cop1 和 cop2

 

学过docker的人应该对镜像这个概念不陌生,我们可以把镜像的概念套用在copy上面。

copy概念图如下: 
技术分享图片

copy(浅复制)对于一个复杂对象的子对象并不会完全复制,什么是复杂对象的子对象呢?就比如序列里的嵌套序列,字典里的嵌套序列等都是复杂对象的子对象。对于子对象,python会把它当作一个公共镜像存储起来,所有对他的复制都被当成一个引用,所以说当其中一个引用将镜像改变了之后另一个引用使用镜像的时候镜像已经被改变了。

所以说看这里的origin[2],也就是 [3, 4] 这个 list。根据 shallow copy 的定义,在 cop1[2] 指向的是同一个 list [3, 4]。那么,如果这里我们改变了这个 list,就会导致 origin 和 cop1 同时改变。这就是为什么上边 origin[2][0] = “hey!” 之后,cop1 也随之变成了 [1, 2, [‘hey!’, 4]]。

deepcopy概念图如下: 
技术分享图片

deepcopy的时候会将复杂对象的每一层复制一个单独的个体出来。 
这时候的 origin[2] 和 cop2[2] 虽然值都等于 [3, 4],但已经不是同一个 list了。即我们寻常意义上的复制。









以上是关于Python的赋值与复制的主要内容,如果未能解决你的问题,请参考以下文章

Python中的赋值(复制)浅拷贝与深拷贝

关于赋值与深浅复制

python的复制,深拷贝和浅拷贝的区别

Python学习教程:Python列表赋值,复制,深拷贝及5种浅拷贝详解

Python面试题目之Python的复制和赋值浅析

python中的两个赋值运算符有啥区别? [复制]