Python——赋值、浅拷贝、深拷贝的区别

参考自:Python——赋值、浅拷贝、深拷贝

basic concepts:

1,变量:是一个系统表的元素,拥有指向对象的连接空间
2,对象:被分配的一块内存,存储其所代表的值
3,引用:是自动形成的从变量到对象的指针
4,注意:类型(int类型,long类型(python3已去除long类型,只剩下int类型的数据))属于对象(object),不是变量(instance)
5,不可变对象:一旦创建就不可修改的对象,包括str,tuple,int,float
6,可变对象:可以修改的对象,包括list,dict,set

赋值

不可变对象(以str为例)

>>> a = 'hello'
>>> b = 'hello'
>>> c = a
>>> [id(x) for x in [a,b,c]]
[2890953102144, 2890953102144, 2890953102144]

由以上指令中,我们可以发现a, b, c三者的地址是一样的。所以以上赋值的操作就相当于c = a = b = ‘hello’。

赋值是系统先给一个变量或者对象(这里是’hello’)分配了内存,然后再将地址赋给a, b, c。所以它们的地址是相同的。

即对于b的情况,由于内存中已经有了‘hello’,因此不会再创建新的字符串,而是直接把b也指向同一个地址

可变对象(以list为例,并且list元素的内容和上面相同)

>>> a = ['hello']
>>> b = ['hello']
>>> c = a
>>> [id(x) for x in [a,b,c]]
[2890966289928, 2890966289672, 2890966289928]

因为str是不可变的,所以同样是’hello’,但是list是可变的,所以必须分配两个地址。

修改:

不可变对象(接上面str例)

>>> a = ['hello']
... b = ['hello']
... c = a
>>> [id(x) for x in [a,b,c]]
[2890966288776, 2890966288968, 2890966288776]
>>> id(a[0])
2890953102144
>>> a[0] = 'world'
>>> id(a[0])
2890966747040
>>> [id(x) for x in [a,b,c]]
[2890966288776, 2890966288968, 2890966288776]
>>> print(a,b,c)
['world'] ['hello'] ['world']

这时a的地址和值变了,但是b, c地址和值都未变。因为str的不可变性,a要重新赋值则需重新开辟内存空间,所以a的值改变,a指向的地址改变b, c由于’hello’的不变性不会发生改变

即b c所指向的字符串没发生变化,而a的已经指向新的了

可变对象(接上面list的例子)

>>> a = ['hello']
... b = ['hello']
... c = a
... [id(x) for x in [a,b,c]]
>>> [id(x) for x in [a,b,c]]
[2890966289608, 2890966289736, 2890966289608]
>>> a[0] = 'world'
>>> [id(x) for x in [a,b,c]]
[2890966289608, 2890966289736, 2890966289608]
>>> print(a, b, c)
['world'] ['hello'] ['world']

这时a, c的值和地址均改变,但二者仍相同,b不改变。由于list的可变性,所以修改list的值不需要另外开辟空间,只需修改原地址的值。所以a, c均改变。

以上关系可以同下面的图表示
可变对象list的情况

储存指针
储存指针
a
list_1
hello
c
b
list_2
hello

修改a[0]后:

储存指针
储存指针
a
list_1
world
c
b
list_2
hello

不可变对象str的情况:

a
hello
b
c

对a进行赋值a='world’后

a
world
b
hello
c

总结

参考:python基础(5):深入理解 python 中的赋值、引用、拷贝、作用域

对于不可变对象和可变对象来说,浅复制都是复制的引用,只是因为复制不变对象和复制不变对象的引用是等效的(因为对象不可变,当改变时会新建对象重新赋值)。所以看起来浅复制只复制不可变对象(整数,实数,字符串等),对于可变对象,浅复制其实是创建了一个对于该对象的引用,也就是说只是给同一个对象贴上了另一个标签而已。

无论是可变对象还是不可变对象,变量间的赋值都是直接复制引用(就是给原来的内存空间贴新标签)!!
也即复制箭头,在图1中就是a到list_1的引用,图3中就是a到hello的箭头

但由于对于不可变对象,重新进行赋值(即改变的时候)会新建对象进行赋值,因此看起来“对于不可变对象的赋值,实际是复制了一个新的对象”



slicing [] 切片操作:shallow copy

浅拷贝,也即顶层复制,只对最外层的开辟新的内存空间,而内层元素不会重新开辟内存空间。

>>> a = ['hello', [123, 234]]
>>> b = a[:]
>>> [id(x) for x in a,b]
>>> [id(x) for x in [a,b]]
[2890966288392, 2890966392264]
>>> [id(x) for x in a]
[2890953102144, 2890966289288]
>>> [id(x) for x in b]
[2890953102144, 2890966289288]
>>> a[0] = 'world'
>>> a[1].append(345)
>>> print('a = ', a, '\n\r', 'b = ', b)
a =  ['world', [123, 234, 345]] 

 b =  ['hello', [123, 234, 345]]

123
234
a
list_0
b
list_1
hello
list_inner

修改后:

123
234
345
a
list_0
b
list_1
world
list_inner
hello

a中第一个元素str改变,但是b中未改变;a中第二个元素改变,b中也改变。这就符合不可变的对象修改会开辟新的空间,可变的对象修改不会开辟新空间,而是直接在原地址上进行操作

这里的顶层,也就是list_0复制出一个list_1,并把list_1的地址赋给b但是内层元素的指向是相同的

deep copy(本质是递归到最底层,把所有引用都复制)

>>> from copy import deepcopy
>>> a = ['hello', [123, 234]]
>>> b = deepcopy(a)
>>> [id(x) for x in (a, b)]
[2890966267016, 2890966327368]
>>> [id(x) for x in a]
[2890953102144, 2890966327880]
>>> [id(x) for x in b]
[2890953102144, 2890966327496]
>>> [id(x) for x in a[1]] # 看内部list的指向
[1833270080, 1833273632]
>>> [id(x) for x in b[1]] #看内部list的指向
[1833270080, 1833273632]
>>> a[0] = 'world'
>>> a[1].append(345)
>>> print('a = ', a, '\n\r', 'b = ', b)
a =  ['world', [123, 234, 345]] 

 b =  ['hello', [123, 234]]

可见b的值未改变

上述关系又下图表示:

123
234
a
list_0
b
list_1
hello
list_inner_0
list_inner_1

修改后

123
234
345
a
list_0
b
list_1
hello
list_inner_0
list_inner_1

由于内层的list也同样复制了一份(即list_inner_1,并让b[1]指向它),第二条赋值a[1].append(345) 是对list_inner_0进行操作,由于是可变对象,因此直接在原内存地址上进行修改。


总结

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值