浅谈两者理论区别与实操避雷。
不同的 copy 方式与内存地址的变化
用不同的方式构建新的列表,然后查看他们各自的内存地址:
import copy
list1 = [[1, 2], (30, 40)] list2 = list1 list3 = list(list1) list4 = copy.copy(list1) list5 = copy.deepcopy(list1) list6 = list1[0]
print("id list1: ", id(list1)) >> id list1: 4431556800
print("id list2: ", id(list2)) >> id list2: 4431556800
print("id list3: ", id(list3)) >> id list3: 4431510080
print("id list4: ", id(list4)) >> id list4: 4431550528
print("id list5: ", id(list5)) >> id list5: 4431559680
print("id list6: ", id(list6)) >> id list6: 4431557248
|
新列表中各自元素的内存地址?
如果你对浅拷贝有一定的了解,你也许会知道浅拷贝虽然会给新的列表重新分配一块内存,创建一个新的对象,但里面的元素是原对象中各个子对象的引用。基于此,我们继续查看列表中元素的内存地址,看是否和理论一致。
print("id list1[0]: ", id(list1[0])) >> id list1[0]: 4431557248
print("id list4[0]: ", id(list4[0])) >> id list4[0]: 4431557248
print("id list5[0]: ", id(list5[0])) >> id list5[0]: 4431569344
|
我们会发现,浅拷贝的元素地址和原对象保持一致,且正好是我们上面创建的 list6 的内存地址!
浅拷贝 (Shallow Copy) 可能引发的问题
会受到影响的情况
基于上面的结论和发现,我们不难想到,当我们向 list6 添加新的元素时,list1 / list2 / list3 / list4 都会发生变化,因为他们都包含了这个元素,而 list5 则不会受到影响:
list6.append(3)
print(list6) >> [1, 2, 3]
print(list1, list2, list3, list4) >> [[1, 2, 3], (30, 40)], [[1, 2, 3], (30, 40)], [[1, 2, 3], (30, 40)], [[1, 2, 3], (30, 40)]
print(list5) >> [[1, 2], (30, 40)]
|
或者是我们更改 list6 中的元素时:
list6[0] = 99
print(list6) >> [99, 2, 3]
print(list1, list2, list3, list4) >> [[99, 2, 3], (30, 40)] [[99, 2, 3], (30, 40)] [[99, 2, 3], (30, 40)] [[99, 2, 3], (30, 40)]
print(list5) >> [[1, 2], (30, 40)]
|
不会受到影响的情况
但值得注意的是,我们进行上面的操作时,本质上没有更新 list6 的内存地址,而其他的列表引用了这个内存地址,所以才导致了变化。
但是,如果我们的操作也会改变 list6 的内存地址呢?
print("id list6: ", id(list6)) >> id list6: 4431557248
list6 = 66 print("id list6: ", id(list6)) >> id list6: 4391690640
print(list1, list2, list3, list4) >> [[99, 2, 3], (30, 40)] [[99, 2, 3], (30, 40)] [[99, 2, 3], (30, 40)] [[99, 2, 3], (30, 40)]
|
所以,如果使用了浅拷贝,在修改任何相关元素时,最好是弄清楚该元素是否被其他对象引用,以及内存地址在不同的操作中是否发生了变化。尤其是在处理 list、dict、set、自定义类型等可变对象时,更需要谨慎。
深拷贝 (deep copy)
当然,如果要追求绝对稳妥又不担心内存占用,那么深拷贝会是一个很好的应对方法。深拷贝会为新对象重新分配一块内存,并且将原对象中的元素,以递归的方式,通过创建新的子对象拷贝到新对象中。因此,新对象和原对象没有任何关联。
不过,深度拷贝也不是完美的:如果被拷贝对象中存在指向自身的引用,那么程序就会陷入无限循环…
一些补充点
对于 Python 中的不可变对象,比如int
、float
、bool
、str
、tuple
,不管是浅拷贝还是深拷贝,都是引用而不会创建新的。
tuple1 = (1, 2, 3) tuple2 = copy.copy(tuple1) tuple3 = copy.deepcopy(tuple1)
print("tuple1 is tuple2 ?", tuple1 is tuple2) print("tuple1 is tuple3 ?", tuple1 is tuple3)
>> tuple1 is tuple2 ? True >> tuple1 is tuple3 ? True
|
进而我们可以想象,如果我们修改了 list1 中的元组:因为元组是不可变的,这里表示对 list1 中的第二个元组拼接,然后重新创建了一个新元组作为 list1 中的第二个元素,而浅拷贝的 list3 / list4 中没有引用新元组,因此并不受影响。
list1[1] += (50, 60)
print(list1) >> [[99, 2, 3], (30, 40, 50, 60)]
print(list3) >> [[99, 2, 3], (30, 40)]
|
而对于 pandas.DataFrame 而言:
df_2 = df_1
df_2 = copy.copy(df_1) df_2 = df_1.copy()
|