python学习笔记:浅/深拷贝,切片,函数传参

前文讲了一点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指向的对象上

函数传参

函数传参一般有两种机制

  1. 值传递:被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。
  2. 引用传递:被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量

但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.

总结

浅/深拷贝:

  • 浅拷贝中创建了一个新的对象,但该新对象中的可变对象与原对象中的有相同的地址
  • 深拷贝则完全创建了一个新的对象

切片:

  • 对a[:]的修改会直接作用在a指向的对象上

函数传参:

  • python中的函数传参属于传对象引用的方式
  • 若想对形参的修改能影响实参,考虑用切片

参考资料

  1. https://www.cnblogs.com/zhoug2020/p/9110663.html
  2. https://www.cnblogs.com/xiaxiaoxu/p/9742452.html
  3. https://www.cnblogs.com/xueli/p/4952063.html

你可能感兴趣的:(python)