深浅拷贝
Python
目录
深拷贝
神经网络结构复制
def clones(module, N):
"自定义克隆函数,产生N个结构完全相同但是参数独立(deepcopy)的网络层"
"Produce N identical layers."
return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])
class Decoder(nn.Module):
"Generic N layer decoder with masking."
def init(self, layer, N):
super(Decoder, self).init()
self.layers = clones(layer, N)
self.norm = LayerNorm(layer.size)
pandas 数据处理
df_with_quant = df[has_quantitative].copy()
那么,Pandas 中的默认行为是 深拷贝 (Deep Copy)。
- 默认参数:
Pandas 的 copy() 方法原型是 DataFrame.copy(deep=True)。
- 如果不传参数,默认 deep=True。
- 深拷贝 (deep=True):
- 行为:它会复制数据(Data)和索引(Index)。
- 结果:df_with_quant 拥有自己独立的内存空间。如果你修改 df_with_quant 中的数据,不会影响原始的 df。
- 场景:这是绝大多数数据清洗步骤中想要的行为,可以安全地避免 SettingWithCopyWarning 警告。
- 浅拷贝 (deep=False):
- 行为:只有当显式使用 .copy(deep=False) 时发生。它只复制对数据和索引的引用(Reference),不复制实际的数据内容。
- 结果:新旧 DataFrame 共享同一块内存数据。修改其中一个可能会影响另一个(取决于具体的数据类型和修改方式)。
浅拷贝
A. 切片操作 [:] (针对列表)
list_1 = [1, [2, 3]]
list_2 = list_1[:] # 浅拷贝
list_2[0] = 9
list_2[1][0] = 99
print(list_1)
这一句 list_2[0] = 9 做的事情叫做:引用重指向(Rebinding)。
简单来说:它把 list_2 的第 0 个格子里的东西换掉了,而不是修改了原来的那个数字。
这正是为什么 list_1 不受影响的原因。我们需要区分”容器(List)“和”内容(Element)“。
详细图解
1. 初始状态(浅拷贝后)
执行 list_2 = list_1[:] 后,内存里有两个独立的列表外壳(List),但它们装着同样的内容。
- list_1 的第0格贴着标签,指向内存里的数字 1。
- list_2 的第0格贴着标签,也指向内存里的同一个数字 1。
list_1 [第0格] ────┐
│
▼
(数字对象 1)
▲
│
list_2 [第0格] ────┘
2. 执行 list_2[0] = 9 时发生了什么?
这一行代码并不是去”把 1 变成 9”(因为整数在 Python 中是不可变的,你没法改它),而是做了两件事:
- 创建/找到新对象:在内存里找到(或创建)数字 9。
- 撕标签,贴新标签:把 list_2 第 0 格原本指向 1 的连线剪断,重新连向 9。
注意: 此时 list_1 的第 0 格没有任何人去动它,它依然指向 1。
list_1 [第0格] ──────────▶ (数字对象 1) <-- list_1 依然指着这里
list_2 [第0格] ──X (剪断)
│
└───────▶ (数字对象 9) <-- list_2 改指这里了
核心区别:为什么刚才那个 [1][0]=99 会影响?
这是初学者最容易晕的地方,请看这个对比:
情况 A(本题):修改外层索引 list_2[0] = 9
- 操作对象:list_2 自己的格子。
- 动作:替换(把旧引用扔了,换个新的)。
- 结果:因为 list_1 和 list_2 是两个独立的格子(浅拷贝保证了外壳独立),所以我换我家客厅的画,不影响你家客厅的画。
情况 B(上一题):修改内层索引 list_2[1][0] = 99
- 操作对象:list_2 第 1 格所指向的那个公共列表。
- 动作:钻进去修改。
- 逻辑:
- 程序先读取 list_2[1],发现它指向一个列表 [2, 3]。
- 程序也知道 list_1[1] 指向同一个列表 [2, 3]。
- 代码指令是”进入这个列表,把里面的第一个数改掉”。
- 结果:因为两人共用同一个内层列表,所以里面的东西变了,大家都看到了。
总结
- list_2[0] = 9: 是**“换人”**。list_2 说:“我不想要这个 1 了,我要换成 9”。这只跟 list_2 有关。
- list_2[1][0] = 99: 是**“改状态”**。list_2 说:“我要把我和 list_1 共同拥有的那个袋子里的苹果换成香蕉”。因为袋子是共享的,list_1 再打开看时,发现苹果变成了香蕉。
B. 工厂方法 .copy()
适用于列表(List)和字典(Dict)。
dict_1 = {'a': 1, 'b': [2, 3]}
dict_2 = dict_1.copy() # 浅拷贝
dict_2['b'][0] = 999
print(dict_1['b'][0]) # 输出 999
C. copy 模块的 copy()
这是通用的浅拷贝方法。
import copy
obj1 = [1, [2, 3]]
obj2 = copy.copy(obj1) # 浅拷贝
特别注意:赋值 = 不是浅拷贝!
初学者最容易混淆的地方:直接赋值连浅拷贝都不是,它只是引用(别名)。
df1 = pd.DataFrame(...)
df2 = df1 # 这不是拷贝!这是同一个对象贴了两个标签。
- 赋值 (=):完全共享,连外壳都是同一个。
- 浅拷贝:外壳不同(可以说是”换了层皮”),但肉(数据)是一样的。
- 深拷贝:完全独立,你是你,我是我。