可变对象 不可变对象 浅拷贝 深拷贝
可变对象和不变对象
可变对象是指,一个对象在不改变其所指向的地址的前提下,可以修改其所指向的地址中的值;
不可变对象是指,一个对象所指向的地址上值是不能修改的,如果你修改了这个对象的值,那么它指向的地址就改变了,相当于你把这个对象指向的值复制出来一份,然后做了修改后存到另一个地址上了,但是可变对象就不会做这样的动作,而是直接在对象所指的地址上把值给改变了,而这个对象依然指向这个地址。
参考: https://www.cnblogs.com/xiaxiaoxu/p/9742452.html
可变对象与不可变对象的区别在与可变对象可以修改变量的值(即修改变量的值后,内容变了,变量地址不变),而不可变对象修改变量的值后,地址也会变化.
区别:1不变对象可以作为dict的key,而可变对象不行,如list不可作为key,但tuple可以
不可变对象:
# 性质1 # python不可变对象有number str tuple,除了tuple外,有时候内容一样地址就一样https://www.jianshu.com/p/b3157c9751d0 a = 1 print(id(a)) b = 1 print(id(b)) c = a + 0 print(id(c)) p = (1,2,3) q = (1,2,3) # tuple不具有这一性质 print(id(p)) print(id(q)) # 9289312 # 9289312 # 9289312 # 140232056227664 # 140232056227808 # 性质2 # 如下当改变i的值后,内存空间中原先的3会被python的垃圾收集机制自动回收掉, # 计算出的结果5会开辟新的空间来存放,i中存放这个新内容的地址,如果计算结果是2,则地址不会变 i = 3 print(id(i)) i = 3 + 2 print(id(i)) # 9289376 # 9289440 # s执行replace后把里面的“a”换成了“A“相当于创建的新的地址存放”Abc“,s中仍然是abc,返回的是新的内容 # m排序后,m中的值变了,返回是空 # 可见对不可变对象而言,调用它的方法,只会返回一个新对象,原先的对象不变 # 而对可变对象而言,是在原有的对象上直接修改的,返回值为空 s = 'abc' s1 = s.replace('a','A') print('s', s) print('s1', s1) m = [11,2,3] m1 = m.sort() print('m', m) print('m1', m1) # s abc # s1 Abc # m [2, 3, 11] # m1 None
可变对象:
def judge_id(a,b): # is 和 == 的不同在于is用于判断地址是否相同,而==用于判断值 if a is b: print('地址相同') else: print('地址不同') a1 = [1,2,3] # 可变对象没有不可变对象的性质1 a2 = [1,2,3] judge_id(a1,a2) # python变量的赋值都是引用的传递,a1 a3地址相同 a3 = a1 judge_id(a1,a3) # 地址不同 # 地址相同 # 不修改地址的操作 # 修改a3的内容后,a1的内容也改变了,这也是称其为可变对象的原因 # 这些修改a3的方法都是不修改地址的原地修改,所以a1的内容也会变 # a3[0] = 111 # a3.extend([4]) 这几种方法等价,都是原地修改 # a3.append(4) a3 += [4] print(a1,a3) judge_id(a1,a3) # [1, 2, 3, 4] [1, 2, 3, 4] # 地址相同 # 修改地址的操作 # 利用 + 的方法合并list,产生了新对象,真实修改了地址 a3 = a3 + ['a3地址变了'] print(a1,a3) judge_id(a1,a3) # 下面这种赋值方法并不是传递的地址,而是产生了新的对象 a4 = a1[:] judge_id(a1,a4) # [1, 2, 3, 4] [1, 2, 3, 4, 'a3地址变了'] # 地址不同 # 地址不同
浅拷贝和深拷贝
不同点:
深拷贝和浅拷贝需要注意的地方就是可变元素的拷贝:在浅拷贝时,拷贝出来的新对象的地址和原对象是不一样的,但是新对象里面的可变元素(如列表)的地址和原对象里的可变元素的地址是相同的,也就是说浅拷贝它拷贝的是浅层次的数据结构(不可变元素),对象里的可变元素作为深层次的数据结构并没有被拷贝到新地址里面去,而是和原对象里的可变元素指向同一个地址,所以在新对象或原对象里对这个可变元素做修改时,两个对象是同时改变的,但是深拷贝不会这样,这个是浅拷贝相对于深拷贝最根本的区别。list的切片复制是浅拷贝
相同点:
二者对拷贝子对象中的不变对象都是真实的拷贝,修改后原来的内容不会改变
# 浅拷贝 from copy import copy m = [1, 2, [3, 4], [5, [6, 7]]] # 浅拷贝的时候,不同于直接的赋值,m n的地址不同,但里面每个子对象的地址相同 print('m:', id(m)) print([id(i) for i in m]) n = copy(m) print('n:', id(n)) print([id(i) for i in n]) print(n is m) print(n[0] is m[0]) print(n[2] is m[2]) # m: 140017123483144 # [9289312, 9289344, 140017123502216, 140017123483080] # n: 140017123502344 # [9289312, 9289344, 140017123502216, 140017123483080] # False # True # True # 修改n中的不变对象时,m的内容不会被修改 n[0] = -1 print(m) # 修改n中可变对象时,m的内容被修改了 n[2][1] = -1 print(m) # [1, 2, [3, 4], [5, [6, 7]]] # [1, 2, [3, -1], [5, [6, 7]]] # 切片拷贝是浅拷贝 p = [1,2,3] q = p[:] print(id(p),id(q)) # 139986256699016 139986256698760 # 深拷贝 from copy import deepcopy m = [1, 2, [3, 4], [5, [6, 7]]] # 深拷贝的时候,m n的地址不同(浅拷贝也不同),m n子对象中,不变对象的地址相同,可变对象的地址不同 print('m:', id(m)) print([id(i) for i in m]) n = deepcopy(m) print('n:', id(n)) print([id(i) for i in n]) print(n is m) print(n[0] is m[0]) print(n[2] is m[2]) # m: 140121212710408 # [9289312, 9289344, 140121212729480, 140121212710344] # n: 140121212729608 # [9289312, 9289344, 140121212731272, 140121212730248] # False # True # False # 无论修改n中的不变对象还是可变对象,m中的值都不变 n[0] = -1 print(m) n[2][1] = -1 print(m) # [1, 2, [3, 4], [5, [6, 7]]] # [1, 2, [3, 4], [5, [6, 7]]]
注意对浅拷贝的赋值,是直接修改的原对象
# 如下对m[:]赋值的时候,是直接修改的原对象,没有产生新对象 def func(m): m[0] = 20 m[:] = [4, 5, 6] return m m = [1, 2, 3] func(m) print('m的值变了',m) # m的值变了 [4, 5, 6] a = [1,2,3] b = a[:] print(id(a)) print(id(b)) # 140346287988104 # 140346288007240