1、概述
python中涉及到可变对象的赋值操作,整理的浅拷贝和深拷贝相关的内容。
2、一些基本的概念
python变量的赋值和java、c++等有些不同。java和c++在定义变量时候,就指定了变量的类型,如果对该变量赋值其他类型,则会报错。而python是通过对变量地址的引用,来进行赋值的,类型由被指向的变量决定。
1.声明一个变量名,就生成一个系统变量表;
2.声明的时候赋值,就是变量名对值的地址的引用,同时在python的系统类型中确认的变量类型;
3.python的类型只与对象有关,与变量无关;
4.python的变量的申明是动态类型,可以动态指向不同的值,可以通过id函数来查看变量存储的内存
2.1 不可变对象
不可变对象为一些字面值的常量,字符串类型,如字符串 "python" 数字型:int类型,float类型等,如有数字10 100,3.14小数。
对于字符串来说,是不支持原位改变的。
>>> s1 = 'python'
>>> s1[0] = 'j'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> s1.append('3')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'append'

python对于常用的整数,是会有一个整数池,整数都具有相同的地址。

2.2 可变对象
对象的值本身是可以被改变的,如list dict tuple 都是可变对象,tuple比较特殊不支持原位改变,但是可以追加。
2.2.1 直接进行变量的赋值

a = [0,1,2,3,4,5,6,7,8,9]
b = a
print(f'id(a):{id(a)} id(b):{id(b)}')
# 两个变量的地址相同
id(a):2407201034944 id(b):2407201034944
如果修改代码

如果涉及到dict类型有相同的现象。
d ={i:i**2 for i in range(0,11)}
print(d)
d1 = d
print(f'd1 is d:{d1 is d}')
d[0] = 'a'
print(f'修改d后\nd is {d} \nd1 is {d1}')
d1['aaa'] = 'ddd'
print(f'修改d1后\nd is {d} \nd1 is {d1}')
输出:--------------------------------------------------------------------------------------------
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}
d1 is d:True
修改d后
d is {0: 'a', 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}
d1 is {0: 'a', 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}
修改d1后
d is {0: 'a', 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100, 'aaa': 'ddd'}
d1 is {0: 'a', 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100, 'aaa': 'ddd'}

对于tuple有点不同,追加元素,实际是指向了一个新的tuple

t = (1,2,3,4,5,6,7,8,9)
t0 = t
t += ('a','b')
print(t,t0)
t0 += ('c','d')
print(t,t0)
输出:-----------------------------------
赋值后t0和t地址是否相同:True
修改t的值后
t:(1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b')
t0:(1, 2, 3, 4, 5, 6, 7, 8, 9)
修改t0的值后
t:(1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b')
t0:(1, 2, 3, 4, 5, 6, 7, 8, 9, 'c', 'd')
3、可变对象嵌套
以上为简单的场景,每个可变对象内没有再嵌套可变对象。而对于可变对象的嵌套,如果进行赋值,情况则有些不同。
3.1 引用

此时,a和b指向的是相同的list的地址,更改a或者b中任意一个,都会同时修改到a b的值。
a = [0,1,2,3,4,5,6,7,['a','b','c',[11,12,13]],8,9]
b = a
a[8].append('d')
b[8][3].append(14)
print(f'a is:{a}')
print(f'b is:{b}')
输出:
a is:[0, 1, 2, 3, 4, 5, 6, 7, ['a', 'b', 'c', [11, 12, 13, 14], 'd'], 8, 9]
b is:[0, 1, 2, 3, 4, 5, 6, 7, ['a', 'b', 'c', [11, 12, 13, 14], 'd'], 8, 9]

3.2 浅拷贝
浅拷贝可以通过以下几种方式
a = [0,1,2,3,4,5,6,7,['a','b','c',[11,12,13]],8,9]
# 切片的方式是浅拷贝
b = a[:]
# 使用可变对象copy方法
c = a.copy()
# 通过copy模块进行浅拷贝
import copy
d = copy.copy(a)

a = [0,1,2,3,4,5,6,7,['a','b','c',[11,12,13]],8,9]
b = a[:]
print(f'b从a进行浅拷贝,b和a的值相同:{a is b}')
print(f'嵌套的第二层列表是否相同:{a[8] is b[8]}')
print(f'嵌套的第三层列表是否相同:{a[8][3] is b[8][3]}')
输出:-------------------------------------------------------
b从a进行浅拷贝,b和a的值相同:False
嵌套的第二层列表是否相同:True
嵌套的第三层列表是否相同:True
浅拷贝,可变对象中又引用的对象,如list引用的list,浅拷贝至始至终都是一样的,修改这部分值会导致所有浅拷贝后的变量的值同步被修改。
浅拷贝的一个例子:
a = [[1,2,3]]
b = a*3
b[0][0] = 20
print(b)
输出:
[[20, 2, 3], [20, 2, 3], [20, 2, 3]]
a*3指的是将列表中的元素重复3次,然后追加到末尾,这里是浅拷贝,b中的3个元素都是指定同一个list。

3.3 深拷贝
深拷贝将对象完全复制一部分,原始对象和深拷贝后的对象完全没有联系。
import copy
a = [0,1,2,3,4,5,6,7,['a','b','c',[11,12,13]],8,9]
# 切片的方式是浅拷贝
b = copy.deepcopy(a)
a.append(10)
a[8].append('d')
a[8][3].append(14)
print(f'a 修改后\na:{a}\nb:{b}')
b.append('x')
b[8].append('y')
b[8][3].append(999)
print(f'b 修改后\na:{a}\nb:{b}')
输出:
a 修改后
a:[0, 1, 2, 3, 4, 5, 6, 7, ['a', 'b', 'c', [11, 12, 13, 14], 'd'], 8, 9, 10]
b:[0, 1, 2, 3, 4, 5, 6, 7, ['a', 'b', 'c', [11, 12, 13]], 8, 9]
b 修改后
a:[0, 1, 2, 3, 4, 5, 6, 7, ['a', 'b', 'c', [11, 12, 13, 14], 'd'], 8, 9, 10]
b:[0, 1, 2, 3, 4, 5, 6, 7, ['a', 'b', 'c', [11, 12, 13, 999], 'y'], 8, 9, 'x']

浙公网安备 33010602011771号