深度解析Python的拷贝 Python直接赋值 Python浅拷贝 Python深拷贝

Python直接赋值

  • 首先来了解Python的变量:Python 中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。
    在 Python 中,变量就是变量,它没有类型,我们所说的"类型"是变量所指的内存中对象的类型。
    等号(=)用来给变量赋值。
    等号(=)运算符左边是一个变量名,等号(=)运算符右边是存储在变量中的值。
    Python可以同时为多个变量赋值,如a, b = 1, 2 ;一个变量可以通过赋值指向不同类型的对象。
  • 那么“Python 中变量赋值传递时 到底是 引用 还是 拷贝 ”?也即是:“赋值时是 传值 还是 传址”?
    答案是要看:具体操作的是什么类型的数据,如下例↓↓↓
dict = {'a':[1,2,3,4,5],'b':2}
x = dict['a'] # x 指向一份列表
for i in range(5):
    x[i] = 0
print(dict['a']) # 打印出的是 [0, 0, 0, 0, 0]

上面在将"a"的值赋给了x出现了上述情况,如果是将"b"的值赋给了x,当我们修改x的值时,字典dict的值则不收影响如下↓↓↓

dict = {'a': [1, 2, 3, 4, 5], 'b': 2}
x = dict['b']
print(dict)  # {'a':[1,2,3,4,5],'b':2}
print(x)  # 2
x = x + 1
print(x)  # 3
  • 判断:变量赋值传递时是 传值(拷贝),还是传址(引用)的规则
    Python 赋值过程中不明确区分拷贝和引用,但是可以说是“浅拷贝”当创建一个对象,然后把它赋给另一个变量的时候,python并没有拷贝这个对象,而只是拷贝了这个对象的引用。

    一般对静态变量的传递为拷贝,对动态变量的传递为引用。(注,对静态变量首次传递时也是引用,当需要修改静态变量时,因为静态变量不能改变,所以需要生成一个新的空间存储数据)。
    (1)字符串,数值,元组均为静态变量
    (2)列表,字典为动态变量。如:list类型的对象进行append操作时,实际上追加的是该对象的引用,

lis = []
num = [2]
lis.append( num )
id( num ) == id( lis[0] )
输出: True
-------
lis = []
num = [2]
lis.append(num)
print(lis) # [[2]]
num.append(2) 
print(lis) # [[2, 2]] 修改下num的值,结果也会引发list发生变化,所以容易引发错误
  • 变量有时比较复杂,存在组合现象,比如字典中包含列表,列表中包含字典,但赋值时,总是属于某个类型。如果实在不清楚状况,可以试验一下,用id()这个函数看看,如果是引用,两个变量指向的地址是相同的。例如:
>>> a=6
>>> id(a)
10413476
>>> b=a
>>> id(b)
10413476
>>> b=8
>>> id(b)
1041345
'''
修改变量b之前,a和b指向的地址是相同的,修改b后,地址就变了。
'''

Python浅拷贝

  • 什么叫浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象
  • 当我们不知道是引用还是拷贝的情况下,可以显式的拷贝。比如字典、列表 对象本身都具有拷贝的方法:dict.copy()、list.copy()
x = dict.copy()
  • 没有拷贝方法的对象,也是可以显示拷贝。引入copy模块提供的一个copy()方法。
  • 记住引入copy模块的 copy.copy() 是浅拷贝
# 说明只是为了演示,因为dict本身自带copy(),不需要通过引入模块的copy也行,但两者都是浅拷贝
import copy
dict = {'a': [1, 2, 3, 4, 5], 'b': 2}
x = copy.copy(dict['a'])
for i in range(5):
    x[i] = 0
print(dict)  # {'a': [1, 2, 3, 4, 5], 'b': 2}
print(x) # [0, 0, 0, 0, 0]
  • 上述为什么字典没有变,因为copy.copy()是浅拷贝,浅拷贝只会拷贝父对象,即只拷贝第一层,
    因为dict[‘a’]就等于一个列表[1, 2, 3, 4, 5],x拿的就是直接拿到一层,所以再度修改x后,dict没变;可以测试 如果dict = {‘a’: [[1,6,5], 2, 3, 4, 5], ‘b’: 2} 再拿第一层,此时dict是会变,
    或者,x可以整个拷贝dict,再给x赋值,dict也会发生变化,原因是因为,dict 里不止一层,而
    浅拷贝只会改变第一层父对象,如果原对象还有更多层,只会进行引用,即共用同一地址,所以修改一处,都会发生改变,如下面的测试↓↓↓
import copy

dict = {'a': [1, 2, 3, 4, 5], 'b': 2}
x = copy.copy(dict) 
print("拷贝后的x是:%s" % x)
for i in range(5):
    x['a'][i] = 0
print("重新赋值后的x是:%s" % x)
print("看下原来的字典是否发生变化:%s" % dict)

'''
# 输出结果为:
拷贝后的x是:{'a': [1, 2, 3, 4, 5], 'b': 2}
重新赋值后的x是:{'a': [0, 0, 0, 0, 0], 'b': 2}
看下原来的字典是否发生变化:{'a': [0, 0, 0, 0, 0], 'b': 2}
'''

Python深拷贝

  • 深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象
  • 深拷贝必须引入copy模块,然后用其中的deepcopy方法即:copy.deepcopy()
import copy

dict = {'a': [1, 2, 3, 4, 5], 'b': 2}
print("开始dict的内存地址是:%s"%id(dict))
x = copy.deepcopy(dict) 
print("拷贝后的x是:%s" % x)
print("拷贝后的x的内存地址是:%s"%id(x))
for i in range(5):
    x['a'][i] = 0
print("重新赋值后的x是:%s" % x)
print("重新复值后x的内存地址是:%s"%id(x))
print("看下原来的字典是否发生变化:%s" % dict)
print("看下原dict的内存地址是否发生变化:%s"%id(dict))

'''
# 输出结果

开始dict的内存地址是:1408523301152
拷贝后的x是:{'a': [1, 2, 3, 4, 5], 'b': 2}
拷贝后的x的内存地址是:1408553103576
重新赋值后的x是:{'a': [0, 0, 0, 0, 0], 'b': 2}
重新复值后x的内存地址是:1408553103576
看下原来的字典是否发生变化:{'a': [1, 2, 3, 4, 5], 'b': 2}
看下原dict的内存地址是否发生变化:1408523301152

'''
  • 看一看到,深拷贝,完全是拷贝了一份内容一致,但是 完全独立的空间(内存地址不一样),两者互不干扰

总结

  • 直接赋值:其实就是对象的引用(别名)

    浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。

    深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。

  • 浅拷贝和深拷贝实例

# 浅拷贝
>>>a = {1: [1,2,3]}
>>> b = a.copy()
>>> a, b
({1: [1, 2, 3]}, {1: [1, 2, 3]})
>>> a[1].append(4)
>>> a, b
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})

# 深拷贝
>>>import copy
>>> c = copy.deepcopy(a)
>>> a, c
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})
>>> a[1].append(5)
>>> a, c
({1: [1, 2, 3, 4, 5]}, {1: [1, 2, 3, 4]})
  • 图解
    (1)b = a: 赋值引用,a 和 b 都指向同一个对象。
    在这里插入图片描述
    (2)b = a.copy(): 浅拷贝, a 和 b 是一个独立的对象,但他们的子对象还是指向统一对象(是引用)。
    在这里插入图片描述
    (3)b = copy.deepcopy(a): 深度拷贝, a 和 b 完全拷贝了父对象及其子对象,两者是完全独立的。
    在这里插入图片描述
import copy
a = [1, 2, 3, 4, ['a', 'b']] #原始对象
 
b = a                       #赋值,传对象的引用
c = copy.copy(a)            #对象拷贝,浅拷贝
d = copy.deepcopy(a)        #对象拷贝,深拷贝
 
a.append(5)                 #修改对象a
a[4].append('c')            #修改对象a中的['a', 'b']数组对象
 
print( 'a = ', a )
print( 'b = ', b )
print( 'c = ', c )
print( 'd = ', d )

'''
('a = ', [1, 2, 3, 4, ['a', 'b', 'c'], 5])
('b = ', [1, 2, 3, 4, ['a', 'b', 'c'], 5])
('c = ', [1, 2, 3, 4, ['a', 'b', 'c']])
('d = ', [1, 2, 3, 4, ['a', 'b']])
'''

拓展

  • 假设有一个长数组和一个稍短的数组(列表),现在有一个这样的需求:长数组需要拷贝到稍短数组的全部内容,然后自身超出的部分,不需要改变或者可以用稍短数组最后一位来填充
  • 联想到java中可以用: Arrays.copyOf(稍短数组,长数组),效果是 长数组或有稍短数组的全部内容,其他超出部分默认用0填充
  • 那Python该怎样完成呢?此时可以借助切片来完成
long_array = [0] * 10 # 长数组
short_array = [1, 2, 3, 4, 5] # 稍短数组
long_array[:len(short_array)]=short_array.copy() # 直接使用自带的显示copy
print(long_array) # [1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
# 也可以用copy模块,如果拷贝内容需要独立使用,还可以用深拷贝
import copy

long_array = [0] * 10 # 长数组
short_array = [1, 2, 3, 4, 5] # 稍短数组
long_array[:len(short_array)] = copy.copy(short_array)
# long_array[:len(short_array)] = copy.deepcopy(short_array)
print(long_array)
# 超出部分 填充 稍短数组的最后一位
import copy

long_array = [0] * 10
short_array = [1, 2, 3, 4, 5]
long_array[:len(short_array)] = copy.copy(short_array)
# long_array[:len(short_array)] = copy.deepcopy(short_array)
# long_array[:len(short_array)] = short_array.copy()

for i in range(len(short_array), len(long_array)):
    long_array[i] = short_array[-1]
print(long_array) # [1, 2, 3, 4, 5, 5, 5, 5, 5, 5]
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值