您的位置:首页 > 编程语言 > Python开发

Python学习笔记(5):赋值、浅拷贝、深拷贝

2017-05-23 01:27 447 查看
与很多编程语言一样,Python的拷贝方式分为赋值、浅拷贝、深拷贝。

在学习过程中,我对拷贝的了解很模糊。在经过一系列的实验后,我对这三者的概念有了进一步的理解。

1. 赋值

首先,我们要对赋值操作有以下认识:

赋值是把一个对象的内存地址赋给一个变量,让变量指向该地址( 旧瓶装旧酒。旧瓶指变量, 旧酒指对象的内存地址,旧是指变量和数据产生后就保留在内存中)。

修改不可变类型(str、tuple)需要开辟新的内存地址空间

修改可变类型(list等)不需要开辟新的内存地址空间

我们通过一些例子来分析下赋值操作:

1.1 字符串赋值

同一str的内存地址唯一,不同str占用不同的内存地址

a = 'hello'
b = 'hello'
c = a

print(a, b, c)
# hello hello hello
[id(x) for x in [a,b,c]]
# [4526716328, 4526716328, 4526716328]


我们可以发现a, b, c三者的id是一样的,以上操作相当于c = a = b = ‘hello’,为什么呢?因为str是不可变类型,所以’hello’只有一个内存地址,a, b, c三者的id一样。赋值是系统先给一个变量或者对象(这里是’hello’)分配了内存,然后再将id赋给a, b, c,所以它们的id是相同的。

变量重新赋值str

a = 'world'

print(a, b, c)
# world hello hello
[id(x) for x in [a,b,c]]
# [4526651464, 4526716328, 4526716328]


这时a的id和值变了,但是b, c的id和值都未变。因为str是不可变类型,不同str占用不同的内存空间。重新赋值a需要新开辟内存空间,再把a指向新的str内存地址,所以当a的str值改变,a指向的内存地址也会改变。b, c由于字符串’hello’的不变性,所以id和值都不会发生改变。

1.2 列表赋值

任意一个list占用不同的内存地址,无论元素是否相同

a = ['hello']
b = ['hello']
c = a

print(a, b, c)
# ['hello'] ['hello'] ['hello']
[id(x) for x in [a, b, c]]
# [4535290824, 4535290888, 4535290824]


为何a和b的id不同?因为list是可变类型,任意一个list占用不同的内存地址。

修改可变类型list,不需要开辟新的内存地址

a.appand(12)

print(a, b, c)
# ['hello', 12] ['hello'] ['hello', 12]
[id(x) for x in [a,b,c]]
# [4535290824, 4535290888, 4535290824]


a和c的值均改变,id没有改变;变量b的id和值都没有改变。由于list的可变性,所以修改list的值不需要另外开辟空间,只需修改原地址的值,所以a, c的值均改变,id没有改变。

对于数字,字符串和其他原子类型对象等,没有被拷贝的说法。即便是用深拷贝,查看id也是一样的;如果对其重新赋值,也只是新创建一个对象,替换掉旧的而已。

2. 浅拷贝

所谓浅拷贝就是对引用的拷贝。

2.1 利用切片操作和工厂方法(list方法)拷贝

代码场景:有一个小伙jack,tom通过切片操作拷贝jack,anny通过工厂方法拷贝jack。

jack = ['jack', ['age', 20]]

tom = jack[:]
anny = list(jack)

print(id(jack), id(tom), id(anny))
# 4570999880 4570944008 4570999752


从id来看,三者是不同的对象。

2.2 修改原对象的值

tom[0] = 'tom'
anny[0] = 'anny'
anny[1][1] = 18

print(jack, tom, anny)
# ['jack', ['age', 18]] ['tom', ['age', 18]] ['anny', ['age', 18]]

print(id(jack), id(tom), id(anny))
# 4570999880 4570944008 4570999752


奇怪的事情发生了,jack、tom、anny的年龄都变成18了。jack、tom、anny他们应当都是不同的对象,怎么会互相影响呢?看下jack,tom,anny的内部元素每个元素id:

print([id(x) for x in jack])
# [4570490672, 4571499784]

print([id(x) for x in tom])
# [4570491232, 4571499784]

print([id(x) for x in anny])
# [4570490616, 4571499784]


恍然大悟,原来jack、tom、anny的年龄指向的是同一个list元素,修改了其中一个,当然影响其他人了。为了便于理解,我画了一个草图:



利用切片操作和工厂方法(list方法)拷贝叫浅拷贝。它只是拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已。

3. 深拷贝

利用copy中的deepcopy方法进行拷贝就叫做深拷贝,外围和内部元素都进行了拷贝对象本身,而不是引用。它是完全拷贝了一个副本,变量内部元素id都不一样。

3.1 copy.deepcopy

我们利用copy模块中的deepcopy方法进行深拷贝。

import copy

jack = ['jack', ['age', '20']]
tom = copy.deepcopy(jack)
anny = copy.deepcopy(jack)

print([id(x) for x in [jack, tom, anny]])
# [4526691976, 4526690440, 4526692936]。因为list是可变类型,所以jack, tom, anny的id不同

print([id(x) for x in jack])
# [4526653368, 4526690568],'jack'是str不可变类型,['age','20']是list可变类型

print([id(x) for x in tom])
# [4526653368, 4526644488]

print([id(x) for x in anny])
# [4526653368, 4526642120]


深拷贝后,可以发现jack, tom,anny的id以及元素值均不相同。这才是完全拷贝了一个副本 。

3.2 修改原数据

tom[0] = 'tom'
anny[0] = 'anny'

print(jack, tom, anny)
# ['jack', ['age', '20']] ['tom', ['age', '20']] ['anny', ['age', 20]]

anny[1][1] = 18
tom[1].append(24)
print(jack, tom, anny)
# ['jack', ['age', '20']] ['tom', ['age', '20',24]] ['anny', ['age', 18]]


修改jack, tom,anny,它们之间不会互相影响。打印出每个人的内部元素id。

print([id(x) for x in [jack, tom, anny]])
# [4526691976, 4526690440, 4526692936]

print([id(x) for x in jack])
# [139132064, 3073507244L]

print([id(x) for x in tom])
# [139137464, 139132204]

print([id(x) for x in anny])
# [139141632, 139157548]


深拷贝后,jack,tom,anny元素id均不同,内部元素也都指向了不同的对象。这是完全拷贝的一个副本,修改jack后,tom没有发生改变,因为tom是一个完全的副本,元素id与jack均不同,jack修改不影响tom。

4. 总结

赋值是把一个对象内存地址赋给一个变量,让变量指向该内存地址( 旧瓶装旧酒 )

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

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

5. 参考文章

python中的深拷贝和浅拷贝理解

Python中的赋值、浅拷贝、深拷贝介绍
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: