深浅拷贝
普通赋值
1 # 1 普通赋值 2 a = [1, 2, 3] 3 b = [11, 22, 33] 4 c = [a, b] 5 6 d = c 7 8 print(id(d)) # 2517490235456 9 print(id(c)) # 2517490235456 10 print(f"c={c}") # c=[[1, 2, 3], [11, 22, 33]] 11 print(f"d={d}") # d=[[1, 2, 3], [11, 22, 33]] 12 print(id(c[1])) # 1234100025024 13 print(id(d[1])) # 1234100025024 14 15 ''' 16 普通的赋值就是引用的拷贝。就像windows快捷键一样,本质上指向的同一个东东 17 '''
浅拷贝(shallow copy)
浅拷贝可变类型
1 import copy # 1. 导入copy模块 2 # 2 浅拷贝可变类型 3 a = [1, 2, 3] 4 b = [11, 22, 33] 5 c = [a, b] 6 7 d = copy.copy(c) # 2. 进行浅拷贝 8 9 print(id(d)) # 2260319076224 10 print(id(c)) # 2493865459584 11 12 ''' 13 浅拷贝就是可变类型操作,就是创建开辟一个新空间,存储拷贝对象 14 '''
浅拷贝深层可变类型
1 import copy # 1. 导入浅拷贝模块 2 3 # 浅拷贝-可变类型-深层数据 4 a = [1, 2, 3] 5 b = [11, 22, 33] 6 c = [a, b] 7 8 d = copy.copy(c) # 2. 对深层数据-可变类型进行浅拷贝 9 10 # 都是执行a列表 11 print(id(a)) # 1890180887936 12 print(id(c[0])) # 1890180887936 13 print(id(d[0])) # 1890180887936
说明前拷贝对可变类型拷贝,只拷贝了一层。
浅拷贝不可变类型
1 import copy 2 3 # 浅拷贝不可变类型 4 a = (1, 2, 3) 5 b = (11, 22, 33) 6 c = (a, b) 7 8 d = copy.copy(c) 9 10 print(id(d)) # 1565409147712 11 print(id(c)) # 1565409147712 12 ''' 13 对不可变类型进行浅拷贝,只是拷贝了引用 14 '''
深拷贝(deep copy)
深拷贝可变类型
1 import copy 2 3 # 深拷贝可变类型 4 a = [1, 2, 3] 5 b = [11, 22, 33] 6 c = [a, b] 7 8 d = copy.deepcopy(c) 9 10 print(id(c)) # 2384833019136 11 print(id(d)) # 2384833019776 12 13 print(id(c[0])) # 2384832984448 14 print(id(d[0])) # 2384833043264 15 16 ''' 17 对可变类型的每一层可变类型都拷贝了一份 18 '''
深拷贝-深层数据的可变类型
1 import copy 2 3 # 深拷贝-深层数据 4 a = [1, 2, 3] 5 b = [11, 22, 33] 6 c = [a, b] # 此处是拷贝了引用 7 8 d = copy.deepcopy(c) 9 10 print(id(a)) # 1490657047936 11 print(id(c[0])) # 1490657047936 12 print(id(d[0])) # 1490657106752 变化了
深拷贝-拷贝不可变类型
1 import copy 2 3 # 深拷贝不可变类型 4 a = (1, 2, 3) 5 b = (11, 22, 33) 6 c = (a, b) 7 8 d = copy.deepcopy(c) 9 10 print(id(c)) # 2367734086464 11 print(id(d)) # 2367734086464 12 13 ''' 14 深拷贝对不可变类型只是拷贝引用 15 '''
总结

1. 思考下面的代码运行出的结果是什么:
list = [0, 1, 2]
list[1] = list
print(list)
参考答案:
[0, [...], 2]
list的第2个元素指向list本身, 因此打印时,就无限循环。[0, [...], 2] 中[...]循环引用,指示列表中的某个位置引用了它自己,而不是无限递归地展开整个结构导致内存耗尽。
2. 如何得出list = [0, [0, 1, 2], 2]这样的结果?
参考答案: list[1] = list[:] 列表的切片其实是浅拷贝
3. 经过以下步骤, a和b分别是多少? 为什么?
a = [0, [1, 2], 3]
b = a[:]
a[0] = 8
a[1][1] = 9
参考答案: a = [8, [1, 9], 3]
b = [0, [1, 9], 3]
a[:]是浅拷贝, 只对对象最顶层进行拷贝
4. 如何保证第3题中最终b = [0, [1, 2], 3]
参考答案: 使用深拷贝
5. 写出下面代码执行结果:
L = [1, 2]
M = L
L = L + [3, 4]
print(L)
print(M)
输出结果:
[1, 2, 3, 4]
[1,2]
6. 写出下面代码执行结果:
L = [1, 2]
M = L
L += [3, 4]
print(L)
print(M)
输出结果:
[1, 2, 3, 4]
[1, 2, 3, 4]
说明:
1. L += [3, 4]是L本身加上[3, 4] 等效于 L.extend([3, 4]),即将 [3, 4] 添加到列表 L 中。由于列表是可变对象,这个操作会在现有的列表上进行修改,而不是创建一个新列表。因此,L 现在包含元素 [1, 2, 3, 4]。
2. 而L = L + [3, 4] 先计算等号右边, 得到一个新的引用, 指向新的L(等号右边的L是原来的L, 等号左边的是一个新的L) 练习6
7. 执行以下代码:
def foo(a, b, c=[]):
c.append(a)
c.append(b)
print(c)
foo(1, 2) # [1, 2] python解释器没有接收到c的参数, 使用默认的空列表(缺省参数)
foo(1, 2, []) # [1, 2] python解释器接收到新的c的参数, 就是用新的空列表, 此时, 缺省参数的列表已经变为[1, 2]
foo(3, 4) # [1, 2, 3, 4] python解释器没有接收到c的参数, 继续使用默认的缺省列表, 即[1, 2].append[3, 4]
foo(1, 2) # [1, 2, 3, 4, 1, 2]
foo(1, 2, []) # [1, 2]
说明:
-
foo(1, 2):在第一次调用函数foo时,没有提供c参数,因此默认会使用函数定义中的默认空列表。这是因为默认参数在函数定义时会被创建一次,然后在后续的函数调用中会继续使用相同的对象。在这里,当你在第一次调用中修改了默认列表,后续的调用会继续使用这个已经被修改的列表。 -
foo(1, 2, []):在这次调用中,你提供了一个新的空列表作为c的参数,因此这个新的空列表会被使用。这不会影响到默认参数,因此在后续调用中默认参数依然是已经被修改的列表。 -
foo(3, 4):这次调用没有提供c参数,所以继续使用之前被修改过的默认列表。所以这次的输出为[1, 2, 3, 4]。 -
foo(1, 2):与第一个调用类似,继续使用之前被修改过的默认列表。因此,输出会是[1, 2, 3, 4, 1, 2]。 -
foo(1, 2, []):与第二次调用类似,你提供了一个新的空列表作为c的参数,所以输出为[1, 2]。
总之,这段代码展示了默认参数在函数定义时只会被创建一次,并在后续的函数调用中持续使用。如果默认参数是可变对象(如列表),它可能会在多次函数调用中累积修改。为了避免这种情况,可以将默认参数设置为不可变对象,如 None,然后在函数内部根据需要创建新的对象。

浙公网安备 33010602011771号