在理解深拷贝与浅拷贝前,先理解下什么是引用。
大家学python的时候都会看到这一次句话。python的所有变量都是引用,引用是什么?
python内存模型简单的可以理解为堆空间,以及栈空间。堆空间存的是对象数据,栈空间存的的是该对象数据的内存地址,该地址指向堆空间的对象数据,这个指向也称为引用。举一些例子来看。
示例1:
a=[1,2,3]
b=a
print(id(b))
print(id(a))
如下图a和b都是存的是1000内存地址,而不是对象本身。
那会有个疑问,如果把列表改了,那是否引用的地址也变了
示例2:
a=[1,2,3]
b=a
print(id(b))
print(id(a))
a[1]=4
print(id(b))
print(id(a))
答案是否定的。因为列表里的数据也都是引用,而不是对象本身。我们通过修改list里的数据不会影响a本身指向的地址,而是修改了a[1]的对象地址。
再看个示例:
通过切片的方式将数据传给c
示例3:
a=[1,2,3]
b=a
print(id(b))
print(id(a))
c=a[:]
print(id(b))
print(id(a))
print(id(c))
结果发现,c虽然跟a的值是一样的,但是却不是同个内存地址。原因是因为列表的切片是创建一个新的列表序列,再将新的对象数据地址赋值给c。
将上述的a数据换个二维数组
示例4:
a=[1,2,[4,5,6]]
b=a
print(id(b))
print(id(a))
a[1]=4
print(id(b))
print(id(a))
c=a[:]
print(id(b))
print(id(a))
print(id(c))
c[2][1]=8
print(a)
print(c)
[1, 4, [4, 8, 6]]
[1, 4, [4, 8, 6]]
结果可以发现,c变了,a也还是会变。
原因是因为列表切片后生成的新的列表对象,指向的地址也会变。但列表里面存的对象地址,并没有变更。所以还是跟a是一样的。简单的理解如下图。a跟c 都是存了x1,x2,x3地址。 c[2][1]=8意味着x32对象引用变了,由5地址变为了8地址。所以所有有x3引用的数据都会变更。
所以,可以简单认为变量的赋值以及切片都是浅拷贝,复制的是对象数据的内存地址。
可以通过打印列表元素地址可以看出是否一样:
for i in c:
print(id(i))
for i in a:
print(id(i))
那如果修改c而不影响a,那就需要将a对象全部拷贝一份数据到新的内存中。这个过程叫做深拷贝。需要通过deepcopy来完成
c=copy.deepcopy(a)
for i in c:
print(id(i))
for i in a:
print(id(i))
这样你会发现。两个列表的list的元素存放地址是不一样的。
那我们再换个示例:
示例5:
a=[1,2,[4,5,6]]
c=[1,2,[4,5,6]]
print(id(a))
print(id(c))
for i in c:
print(id(i))
for i in a:
print(id(i))
a会跟c一样吗?答案是不一样的。因为直接复制既不是深拷贝也不是浅拷贝,而是直接创建新的对象数据给地址引用。
当然python为了省内存,[-5~256]的范围内,都是固定的地址,不会创建新的地址。还有字符串的变量申明也一样,如果存在类似的字符串,python不会额外创建新的字符串内存空间,而是会直接引用。需要注意的是,pycharm会对这些整数以及字符串做优化处理,结果可能是一样的,所以以下示例请使用命令行运行。
a=1234
b=1234
print(id(a))
print(id(b))
a='abcd'
b='abcd'
print(id(a))
print(id(b))
结果内存地址是不一样的。
总结:
python所有的变量=直接赋值都是创建新的对象,引用新对象地址(保留的整数以及字符串除外),=直接赋值没有浅拷贝深拷贝说法。
变量赋值a=b 都是引用拷贝对象的地址,也就是浅拷贝。
列表或者元组切片是第一层深拷贝,第二层以上浅拷贝。
如果想要完全的拷贝另一个列表或者元组的变量,则需要deecopy深拷贝。
疑问:如果是for 循环append后是深拷贝还是浅拷贝呢,欢迎留言
a=[1,2,[4,5,6]]
c=[]
for i in a:
c.append(i)
c[1]=22
c[2][1]=8
print(a)
print(c)