前文讲了一点python的对象机制,其实是为了本文服务的。
为变量赋值(其实是为对象建立引用)一般有三种方式:直接赋值,浅拷贝和深拷贝
直接赋值就是常见的"a=b"之类,将a作为b指向的对象的一个新的引用。
下面主要介绍浅拷贝和深拷贝以及两者之间的区别:
在浅拷贝时,拷贝出来的新对象的地址和原对象是不一样的。
但是新对象里面的可变元素(如列表)的地址和原对象里的可变元素的地址是相同的.
举个直观的栗子:
import copy
a = [1, 2, 3, 4, 5, ['a', 'b']]
c = copy.copy(a) # 浅拷贝
print(id(a)) # 1967240408904
print(id(c)) # 1967237100936
print(id(a[5])) # 1967237036872
print(id(c[5])) # 1967237036872
c.append(6)
c[5].append('c')
print(a) # [1, 2, 3, 4, 5, ['a', 'b', 'c']]
从第4,5行中可以看到,a和c指向的对象的地址不同。
但对于a中的可变对象[‘a’, ‘b’](也就是a[5]),a[5]和c[5]指向的对象的地址是相同的。
因此在第8,9行中,我们分别试图修改c和c[5]。其中,因为c和a指向的对象不同,因此c.append(6)语句并未对a造成影响。而因为a[5]和c[5]指向了同一个对象,因此对从c[5]的修改会反映到a[5]。
深拷贝就是完全跟原对象没有关系了,现对象和原对象中的可变对象和不可变对象都没有关系。
import copy
a = [1, 2, 3, 4, 5, ['a', 'b']]
c = copy.deepcopy(a) # 深拷贝
print(id(a)) # 1902029118472
print(id(c)) # 1902027348808
print(id(a[5])) # 1902029119048
print(id(c[5])) # 1902029149896
c.append(6)
c[5].append('c')
print(a) # [1, 2, 3, 4, 5, ['a', 'b']]
可以看到,a和c,a[5]和c[5]地址都不相同。无论对c进行怎样修改,都不会对a产生影响(因为两者指向了完全不同的两个对象)
关于切片如何使用,网上有一大把文章,这里就不再赘述了。
这里主要讨论一下切片到底是什么,还有切片应用在函数的一些问题。
切片到底是什么?
比较通俗的一个解释是,切片是原对象的一个副本(下面是切片使用的一个例子)
a = [1, 2, 3, 4, 5, ['a', 'b']]
print(id(a[:])) # 2245223837896
print(id(a)) # 2245223273416
a[:] = [1]
print(a) # [1]
print(id(a[:])) # 2245223837896
print(id(a)) # 2245223273416
问题来了,从第2,3行可以看出,a和a[:]指向的对象的地址是不同的。
那么为什么对a[:]的重新赋值会反映到a上呢?
并且明明第4行用的是=赋值,a[:]指向对象的地址没有改变?
说实话这是我比较困惑的地方,也没有找到一个比较好的答案。我现在只能理解为:对a[:]的修改会直接作用在a指向的对象上。
函数传参一般有两种机制
但python不同,python中的函数传参属于传对象引用的方式。也就是说,对于实参变量,我们选择新创建一个该变量指向的对象的引用作为形参。
举一个栗子:
def changelist(list2):
print(id(list2)) # 2364877525448
list2 = [1, 2, 3]
print(id(list2)) # 2364877525960
list1 = [1, 2]
print(id(list1)) # 2364877525448
changelist(list1)
print(list1) # [1, 2]
从第2行和第6行可以看到,实参list1和形参list2指向了同一个对象,list1和list2其实只是同一个对象的不同引用
理所当然的,对list2进行重赋值后,list2指向了另一个对象。因此,list2的修改对list1没有影响。
那么,如果我们想要在改变list2的同时改变list1,要怎么做呢?
很简单,用切片。
def changelist(list2):
print(id(list2)) # 1280449204616
list2[:] = [1, 2, 3] # 用切片啦
print(id(list2)) # 1280449204616
list1 = [1, 2]
print(id(list1)) # 1280449204616
changelist(list1)
print(list1) # [1, 2, 3]
代码只修改了一个地方,就是把list2变成了list2[:].有什么区别?
联想上一部分,对a[:]的修改会直接作用在a指向的对象上。因为list1和list2指向的是同一个对象,而第三行语句相当于直接修改了该对象。所以对list2的修改也会影响list1.
浅/深拷贝:
切片:
函数传参: