高频面试题之Python深拷贝和浅拷贝
在Python中聊到深拷贝和浅拷贝这个问题就需要考虑Python的数据类型, 考虑到Python的数据类型就需要考虑变量的存储问题, 接下来我们从 变量, 对象, 引用的概念出发, 再到数据赋值操作, 延申到深浅拷贝, 循序渐进的梳理下!
1. 前置概念
变量: 能够存储临时数据的容器对象: 万物皆对象
变量和对象的关系: 变量是 对象在内存中的存储和地址的抽象表示
引用: 对对象的内存地址的指向
以下是 变量 ,对象地址, 对象存储的指向关系
2.变量赋值操作
a = 10
print('a的地址:', id(a))
-- a的地址: 2766914546256
1.当变量 被 = 号赋值的时候, 底层会存储这个变量, 生成对象地址, 存储数值10
2. 把a变量的值 = 赋值 给b变量, 对象的值10没有变化, 所以变量b的地址和a是一样的, 都指向同一块内存空间
a = 10
b = a
print('a的地址:', id(a))
print('b的地址:', id(b))
-- a的地址: 2766914546256
-- b的地址: 2766914546256
3. 修改以上代码中的a的值, a=20, 此时a的对象地址指向了20的存储位置
a = 10
b = a
a = 20
print('a的地址:', id(a))
print('b的地址:', id(b))
-- a的地址: 2766918794752
-- b的地址: 2766914546256
变量赋值操作小结:
通过以上操作可知Python中采用的是引用传递, 如果想让2个变量不再指向同一个内存地址空间, 我们需要使用拷贝来实现
3.拷贝 copy-deepcopy
Python提供了内置模块copy, 通过 copy() 函数实现浅拷贝, 通过deepcopy()函数实现深拷贝这个时候我们就要考虑2问题: 1. 浅拷贝和深拷贝 2.可变类型和不可变类型
3.1浅拷贝 ---copy
copy---不可变数据类型 int float string tuple
from copy import deepcopy, copy
# 不可变数据类型 int float string tuple
a = 10
print('a的地址:', id(a))
b = copy(a)
a = 20
print('a的地址:', id(a))
print('b的地址:', id(b))
-- a的地址: 2405541964368
-- a的地址: 2405541964688
-- b的地址: 2405541964368 # 浅拷贝a之后 b的地址没有变化
copy---可变数据类型 list dict set
from copy import deepcopy, copy
# 可变数据类型 list dict set --- 就一层
list1 = [1, 2, 3]
list2 = copy(list1)
list1.append(4)
print('list1的地址:', id(list1), list1)
print('list2的地址:', id(list2), list2)
-- list1的地址: 2221170113984 [1, 2, 3, 4]
-- list2的地址: 2221170113728 [1, 2, 3] # 浅拷贝list1之后, list2地址是新的, 同时内部值不受list1的修改所影响
# 可变数据类型 list dict set --- 嵌套
list1 = [1, 2, 3, ['a', 'b']]
list2 = copy(list1)
list1[3].append(4)
print('list1的地址:', id(list1), list1)
print('list2的地址:', id(list2), list2)
print("list1内部['a', 'b']地址:", id(list1[3]), list1)
print("list2内部['a', 'b']地址:", id(list2[3]), list2)
list1的地址: 2153560030336 [1, 2, 3, ['a', 'b', 4]]
list2的地址: 2153560031360 [1, 2, 3, ['a', 'b', 4]] # 深拷贝list1之后, list2地址是新的,
list1内部['a', 'b']地址: 2153560030592 [1, 2, 3, ['a', 'b', 4]]
list2内部['a', 'b']地址: 2153560030592 [1, 2, 3, ['a', 'b', 4]] # 深拷贝 里面的嵌套的元素 地址不变, 会受到 原始list1内部数据的改变而改变
3.2 深拷贝--deepcopy
deepcopy--不可变数据类型 int float string tuple
# 不可变数据类型 int float string tuple
a = 10
print('a的地址:', id(a), a)
b = deepcopy(a)
a = 20
print('a的地址:', id(a), a)
print('b的地址:', id(b), b)
a的地址: 1504512666192 10
a的地址: 1504512666512 20
b的地址: 1504512666192 10 # 深拷贝 a之后 b的地址没有变化
deepcopy---可变数据类型 list dict set
from copy import deepcopy, copy
# 可变数据类型 list dict set
list1 = [1, 2, 3]
list2 = deepcopy(list1)
list1.append(4)
print('list1的地址:', id(list1), list1)
print('list2的地址:', id(list2), list2)
list1的地址: 2284475923840 [1, 2, 3, 4]
list2的地址: 2284475923584 [1, 2, 3] # 深拷贝 list1之后, list2地址是新的, 同时内部值不受list1的修改所影响
# 可变数据类型 list dict set 嵌套
list1 = [1, 2, 3, ['a', 'b']]
list2 = deepcopy(list1)
list1[3].append(4)
print('list1的地址:', id(list1), list1)
print('list2的地址:', id(list2), list2)
print("list1内部['a', 'b']地址:", id(list1[3]), list1)
print("list2内部['a', 'b']地址:", id(list2[3]), list2)
list1的地址: 1747837926400 [1, 2, 3, ['a', 'b', 4]]
list2的地址: 1747837927360 [1, 2, 3, ['a', 'b']] # 深拷贝list1之后, list2地址是新的,
list1内部['a', 'b']地址: 1747837926656 [1, 2, 3, ['a', 'b', 4]]
list2内部['a', 'b']地址: 1747837925568 [1, 2, 3, ['a', 'b']] # 深拷贝 里面的嵌套的元素地址也被拷贝了一份, 所以原始数据修改 它不受影响
4. 函数传参
4.1 参数类型是 不可变类型, 是值传递
# 不可变数据类型 int float string tuple
a = 10
print('传递之前:', id(a))
def func(param):
param = 2 # 修改形参的值
print("传递之后: ", id(param))
func(a)
-- 传递之前: 2155117832784
-- 传递之后: 2155117832528 # 修改之后值变了, 地址也变化
4.2 参数类型是 可变类型和函数, 是引用传递
# 可变数据类型 list dict set
list1 = [1, 2, 3]
print('传递之前:', id(list1))
def func(param):
param.append(4) # 修改形参的值
print("传递之后: ", id(param))
func(list1)
-- 传递之前: 2060275958464
-- 传递之后: 2060275958464 # 修改之后地址没变划