问题
写一段测试程序,我本意是想从一个DataFrame对象复制出来一个独立的副本出来,代码如下:
df = pd.DataFrame()
df1 = df
# 改变df1的元素
df1[row][col] = 'b'
print(df)
print(df1)
结果打印出来的df也改变了。
也就是说,在改变df1的时候df也改变了,它们实际指向的同一块内存。
如何解决这个问题呢,查阅了一些资料,把原理和解决思路作一记录。
两类对象
首先来了解一下python中的两类对象:不可变对象和可变对象。
- 不可变对象:创建后就不可修改的对象,如数值、字符串和无组等
- 可变对象:创建后仍可修改,如列表、字典、集合、DataFrame等
这样说有点不明所以,它的内存意思是:
- 不可变对象:该对象所指向的内存中的值不能被改变。当修改变量时,相当于把原来的值拷贝一次再修改,这会开辟出一块新内存,变量也指向这个新内存地址
- 可变对象:该对象所指向的内存中的值可以被改变。当修改变量时,直接在内存上修改,所有指向这个内存地址的变量都会发生变化
知道了原理,也就可以深入理解一下赋值和拷贝了。
赋值和拷贝
赋值比较常用,也好理解,使用=操作。
拷贝分为浅拷贝和深拷贝,下面分别介绍。
- 赋值:只是复制了新对象的引用,不会开辟新内存。如
a = 1
,实际上先创建变量a,再分配内存存储值1,最后将变量a与该内存地址使用引用对应起来。于是每次取a时,得到的是1 - 浅拷贝:创建一个新对象,内容是原对象的引用。有三种实现方式:切片、工厂和copy函数。注意它只拷贝了最外围对象一层,如果对象内嵌套有对象,那么对于嵌套对象,只得到引用而不会得到副本
- 深拷贝:相对于浅拷贝而言,它拷贝出来的是完全独立的一个副本,即使对象有多层嵌套,也会完全拷贝出来一个全新对象。实现方式是copy模块中的deepcopy函数
从以上分析可以看出,对于不可变对象,赋值、浅拷贝和深拷贝的结果是一样的,如下代码:
a = 10
b = a
b = 20
print('赋值:', a, b)
# 赋值: 10 20
import copy
a = 10
b = a
b = copy.copy(a)
b = 20
print('copy:', a, b)
# copy: 10 20
a = 10
b = a
b = copy.deepcopy(a)
b = 20
print('deepcopy:', a, b)
# deepcopy: 10 20
对可变对象,赋值与拷贝的结果就不同了。
由于浅拷贝对对象是否嵌套结果会有不同,分别示例测试一下。
对于没有嵌套的简单对象,copy和deepcopy的结果一致,都得到了对象的独立副本,副本的改变不会改变原值:
a = [1,2,3]
b = a
b[0] = 8
print('赋值:', a, b)
# 赋值: [8, 2, 3] [8, 2, 3]
import copy
a = [1,2,3]
b = copy.copy(a)
b[0] = 8
print('copy:', a, b)
# copy: [1, 2, 3] [8, 2, 3]
a = [1,2,3]
b = copy.deepcopy(a)
b[0] = 8
print('deepcopy:', a, b)
# deepcopy: [1, 2, 3] [8, 2, 3]
对于有嵌套对象的对象,当修改对象外层元素时,浅拷贝和深拷贝结果一样,而修改嵌套内元素时,只有深拷贝副本不会改变原值:
a = [1,2,3,[1,2]]
b = a
b[3][0] = 8
print('赋值:', a, b)
# 赋值: [1, 2, 3, [8, 2]] [1, 2, 3, [8, 2]]
import copy
a = [1,2,3,[1,2]]
b = copy.copy(a)
b[3][0] = 8
print('copy:', a, b)
# copy: [1, 2, 3, [8, 2]] [1, 2, 3, [8, 2]]
a = [1,2,3,[1,2]]
b = copy.deepcopy(a)
b[3][0] = 8
print('deepcopy:', a, b)
# deepcopy: [1, 2, 3, [1, 2]] [1, 2, 3, [8, 2]]
小结
明白了以上示例后,开篇的问题自然不在话下。
使用赋值对可变对象进行操作,它们引用的仍是同一块内存,且变量的改变是直接改变内存值。
一个变量的改变自然也会影响到另一变量。
使用深拷贝解决即可。