目录


1. print( 坑的信息 )

  • 挖坑时间:2019/01/10
  • 明细



2. 开始填坑

(1) 问题描述

个人觉得深拷贝与浅拷贝里面大有文章,此篇随笔只能算是浅谈。

(2) Python3.7.2 官方文档

(3) 先说结论

  1. 描述“深拷贝与“浅拷贝”时,常与“赋值”进行对比
  2. 简单地说
    • 赋值,就像给原对象取个昵称,无论呼本名还是唤昵称,说的都是同一个事物
    • 深拷贝,拷贝的是原对象内部的元素,拷贝后井水不犯河水,是一个真正的副本
    • 浅拷贝,拷贝的是原对象内部数据的地址,拷贝后藕断丝连,并不是一个真正的副本
  3. 浅拷贝的补充
    • 拷贝后的新对象占用新的空间,但其内部的元素指向原对象内部对应元素的地址
    • 当原对象中的可变元素发生变化时,新对象中的对应元素同步变化
    • 当原对象中的不可变元素重新生成时,新对象中的对应元素保持不变
    • 只拷贝第一层元素,有人称其为“顶层拷贝”
    • 在前期做一个浅拷贝可以防止后期因变量名众多而混乱
    • 可应用于“联合账号”等

  • 对 copy 模块而言

1) 复合对象谈深拷贝与浅拷贝的区别才有意义

  • 复合对象:包含其他对象的对象,如列表、类实例等

  • 深拷贝会构造一个新的复合对象,然后递归地将在原始对象中所有元素的副本对应地写入新复合对象中
  • 浅拷贝会构造一个新的复合对象,然后(在允许的前提下)向其中写入对原始对象的引用

2) 深拷贝常伴随两个问题,而浅拷贝对此无需担心

  • 递归对象(直接或间接包含对自身引用的复合对象)可能导致递归循环
  • 因为深拷贝会复制原对象的一切,所以可能复制过多的内容,例如打算在副本之间共享的数据

3) deepcopy() 通过以下方式避免 2) 的问题:

  • 保存在当前复制过程中已经复制的对象的 memo 字典
  • 让用户定义的类重写复制操作或复制的组件集

4) 补充

  • 它不能拷贝模块、方法、堆栈跟踪、堆栈帧、文件、套接字、窗口、数组等
  • 可以重写 copy.copy(x) 和 copy.deepcopy(x[, memo])

(4) 少废话,上例子

对于可变或包含可变项的容器,有时需要一个可以在不破坏其它容器的情况下更改自身的副本。

# 例1 从对象的地址上看

list1_1 = [1, 2, 3, 4, 5]
list1_2 = list1_1           # Python 中的赋值语句不复制对象,它们会在目标和对象之间创建绑定关系
list1_3 = list1_1[:]        # 通过分配整个列表的切片来制作列表的浅拷贝
list1_4 = list1_1.copy()    # 使用 list.copy() 来制作列表的浅拷贝
                            # 同理,可以使用 dict.copy() 来制作字典的浅拷贝

print("list1_1 =", list1_1)
print("list1_2 =", list1_2)
print("list1_3 =", list1_3)
print("list1_4 =", list1_4)
print('-'*30)

print("id(list1_1) =", id(list1_1))
print("id(list1_2) =", id(list1_2))
print("id(list1_3) =", id(list1_3))
print("id(list1_4) =", id(list1_4))
  • 运行结果

# 例2 从对象的元素上看(我可能有些啰嗦了)

list2_1 = [1, 2, 3, 4, 5]
list2_2 = list2_1           # 赋值
list2_3 = list2_1[:]        # 浅拷贝
list2_4 = list2_1.copy()    # 浅拷贝

print("list2_1 =", list2_1)
print("list2_2 =", list2_2)
print("list2_3 =", list2_3)
print("list2_4 =", list2_4)
print('-'*30)

list2_1[0] = 661
print("* list2_1 =", list2_1)
print("* list2_2 =", list2_2)
print("* list2_3 =", list2_3)
print("* list2_4 =", list2_4)
print('-'*30)

list2_2[1] = 662
print("* list2_1' =", list2_1)
print("* list2_2' =", list2_2)
print("* list2_3' =", list2_3)
print("* list2_4' =", list2_4)
print('-'*30)

list2_3[2] = 663
print("* list2_1'' =", list2_1)
print("* list2_2'' =", list2_2)
print("* list2_3'' =", list2_3)
print("* list2_4'' =", list2_4)
print('-'*30)

list2_4[3] = 664
print("* list2_1''' =", list2_1)
print("* list2_2''' =", list2_2)
print("* list2_3''' =", list2_3)
print("* list2_4''' =", list2_4)
  • 运行结果

# 例3 从元素的地址上看

list3_1 = [0, 1, 2, [3, 4, 5]]
list3_2 = list3_1               # 赋值
list3_3 = list3_1[:]            # 浅拷贝
list3_4 = list3_1.copy()        # 浅拷贝

print("list3_1 =", list3_1)
print("list3_2 =", list3_2)
print("list3_3 =", list3_3)
print("list3_4 =", list3_4)
print('-'*30)

print("id(list3_1[0]) =", id(list3_1[0]))
print("id(list3_2[0]) =", id(list3_2[0]))
print("id(list3_3[0]) =", id(list3_3[0]))
print("id(list3_4[0]) =", id(list3_4[0]))
print('-'*30)

print("id(list3_1[3]) =", id(list3_1[3]))
print("id(list3_2[3]) =", id(list3_2[3]))
print("id(list3_3[3]) =", id(list3_3[3]))
print("id(list3_4[3]) =", id(list3_4[3]))
print('-'*30)

print("id(list3_1[3][0]) =", id(list3_1[3][0]))
print("id(list3_2[3][0]) =", id(list3_2[3][0]))
print("id(list3_3[3][0]) =", id(list3_3[3][0]))
print("id(list3_4[3][0]) =", id(list3_4[3][0]))
  • 运行结果

# 例4.1 关于层数(第 2 层)

list4_1 = [0, 1, 2, [3, 4, 5]]
list4_2 = list4_1
list4_3 = list4_1[:]
list4_4 = list4_1.copy()

print("list4_1 =", list4_1)
print("list4_2 =", list4_2)
print("list4_3 =", list4_3)
print("list4_4 =", list4_4)
print('-'*30)

list4_1[3].append(6)
print("* list4_1 =", list4_1)
print("* list4_2 =", list4_2)
print("* list4_3 =", list4_3)
print("* list4_4 =", list4_4)
print('-'*30)

list4_2[3].append(7)
print("* list4_1' =", list4_1)
print("* list4_2' =", list4_2)
print("* list4_3' =", list4_3)
print("* list4_4' =", list4_4)
print('-'*30)

list4_3[3][0] = 666
print("* list4_1'' =", list4_1)
print("* list4_2'' =", list4_2)
print("* list4_3'' =", list4_3)
print("* list4_4'' =", list4_4)
print('-'*30)

list4_4[3][1] = 888
print("* list4_1''' =", list4_1)
print("* list4_2''' =", list4_2)
print("* list4_3''' =", list4_3)
print("* list4_4''' =", list4_4)
  • 运行结果

从例3 的 id 也不难想象这个结果。


# 例4.2 关于层数(第 1 层;我啰嗦了,此例同例2)

list4_1 = [0, 1, 2, [3, 4, 5]]
list4_2 = list4_1
list4_3 = list4_1[:]
list4_4 = list4_1.copy()

print("list4_1 =", list4_1)
print("list4_2 =", list4_2)
print("list4_3 =", list4_3)
print("list4_4 =", list4_4)
print('-'*50)

list4_1.append(6)
print("* list4_1 =", list4_1)
print("* list4_2 =", list4_2)
print("* list4_3 =", list4_3)
print("* list4_4 =", list4_4)
print('-'*50)

list4_2.extend([7, 8])
print("* list4_1' =", list4_1)
print("* list4_2' =", list4_2)
print("* list4_3' =", list4_3)
print("* list4_4' =", list4_4)
print('-'*50)

list4_3.insert(3, "YorkFish")
print("* list4_1'' =", list4_1)
print("* list4_2'' =", list4_2)
print("* list4_3'' =", list4_3)
print("* list4_4'' =", list4_4)
print('-'*50)

list4_4.insert(0, "YorkFish")
print("* list4_1''' =", list4_1)
print("* list4_2''' =", list4_2)
print("* list4_3''' =", list4_3)
print("* list4_4''' =", list4_4)
  • 运行结果

(5) 召唤深拷贝

# 例5 赋值与浅拷贝、深拷贝的不同

import copy     # 导入 copy 模块

list5_1 = [0, 1, 2, ['a', 'b']]
list5_2 = list5_1                   # 赋值
list5_3 = list5_1.copy()            # 浅拷贝;用 list5_1[:] 效果一样
list5_4 = copy.copy(list5_1)        # 浅拷贝
list5_5 = copy.deepcopy(list5_1)    # 迟来的深拷贝

print("list5_1 =", list5_1)
print("list5_2 =", list5_2)
print("list5_3 =", list5_3)
print("list5_4 =", list5_4)
print("list5_5 =", list5_5)
print('-'*30)

list5_1.append(4)
list5_1[3].append('c')
print("list5_1' =", list5_1)
print("list5_2' =", list5_2)
print("list5_3' =", list5_3)
print("list5_4' =", list5_4)
print("list5_5' =", list5_5)
  • 运行结果

深拷贝的第二层没有像浅拷贝一样随原对象的变化而变化。


(6) 关于不可变元素

# 例6 “不动如山”的不可变元素

import copy

list6_1 = [0, 1, 2, (3, 4, 5)]
list6_2 = list6_1
list6_3 = list6_1.copy()        # 用 list6_1[:] 效果一样
list6_4 = copy.copy(list6_1)
list6_5 = copy.deepcopy(list6_1)

print("list6_1 =", list6_1)
print("list6_2 =", list6_2)
print("list6_3 =", list6_3)
print("list6_4 =", list6_4)
print("list6_5 =", list6_5)
print('-'*30)

print("id(ist6_1[3]) =", id(list6_1[3]))
print("id(ist6_2[3]) =", id(list6_2[3]))
print("id(ist6_3[3]) =", id(list6_3[3]))
print("id(ist6_4[3]) =", id(list6_4[3]))
print("id(ist6_5[3]) =", id(list6_5[3]))
print('-'*30)

list6_1[3] = (6, 6, 6)          # list6_1 与 list6_2 是一体的
print("list6_1' =", list6_1)
print("list6_2' =", list6_2)
print("list6_3' =", list6_3)
print("list6_4' =", list6_4)
print("list6_5' =", list6_5)
print('-'*30)

print("id(ist6_1[3]') =", id(list6_1[3]))
print("id(ist6_2[3]') =", id(list6_2[3]))
print("id(ist6_3[3]') =", id(list6_3[3]))
print("id(ist6_4[3]') =", id(list6_4[3]))
print("id(ist6_5[3]') =", id(list6_5[3]))
print('-'*30)

list6_2[3] = (8, 8, 8)
list6_3[3] = (8, 8, 8)
list6_4[3] = (8, 8, 8)
list6_5[3] = (8, 8, 8)
print("list6_1'' =", list6_1)
print("list6_2'' =", list6_2)
print("list6_3'' =", list6_3)
print("list6_4'' =", list6_4)
print("list6_5'' =", list6_5)
print('-'*30)

print("id(ist6_1[3]'') =", id(list6_1[3]))
print("id(ist6_2[3]'') =", id(list6_2[3]))
print("id(ist6_3[3]'') =", id(list6_3[3]))  # 注意 id
print("id(ist6_4[3]'') =", id(list6_4[3]))  # 注意 id
print("id(ist6_5[3]'') =", id(list6_5[3]))  # 注意 id
  • 运行结果

Python3 能“偷懒”时必“偷懒”,对于可变元素,有变化才分配新地址。


在下学识有限,不足之处,还请诸位多多指教!

01-27 17:46