目录

Python还债日记之对象(二)

Python中对象的一些基础操作,赋值、拷贝等。

前文链接:

Python还债日记之对象(一)


1. 对象的操作之对比

python中有两种对变量的比较方式,分别是 == 以及调用 is 关键词,不同点在于:

  • == 只比较两个变量的值是否相等,相等则返回True

  • is 则即比较两个变量值,又比较两个变量地址,都相等才返回True

is等同于调用id判断地址是否相等,再和操作==判断值是否相等相

2. 对象的操作之赋值

个人理解,在python中,赋值就是将对象的地址通过引用的方式传递给变量,同时对象的引用次数加1

赋值的内在操作是地址的传递,这个地址指对象个体的地址,而不是子对象地址

赋值操作具有以下几点特征:

  • 赋值与被赋值的两个对象,在python中可以认为是完全一样的,用一个变量给另一个变量赋值,其实就是给当前内存中的对象增加一个“标签”而已

    1
    2
    3
    4
    5
    6
    7
    8
    
    >>> a = (11)
    >>> print(id(a))
    10914816
    >>> b = a
    >>> print(id(b))
    10914816
    >>> b is a
    True
    
  • 对于可变对象,如List,赋值时不需要重新开辟空间(因为可变对象是可变的,赋值时相当于给对象添加了一个新的标签)

  • 对于不可变对象,如Tuple、Set等,在赋值时需要开辟新的空间(因为需要破坏原有的引用,将变量的引用指向新的对象)但是python中的小对象整数池、短字符串缓冲区需要特殊考虑

  • 对于可变对象,b是a的赋值,那么改变b的值,或者改变b的值,都会更新另外一方的值

3. 对象的操作之拷贝

拷贝操作有两种,在python中,只拷贝浅层对象的操作叫做浅拷贝,递归拷贝所有对象的操作叫做深拷贝

https://raw.githubusercontent.com/shmilywh/PicturesForBlog/master/2021/04/26-15-57-53-2021-04-26-15-57-50-image.png

3.1 浅拷贝

python中的浅拷贝操作包括:copy 模块的 copy 函数 ,对象的 copy 函数 ,工厂方法,切片

对于不可变对象,浅拷贝操作是传递引用,相当于赋值

对于可变对象,浅拷贝操作是对浅层对象进行拷贝,子对象不变

这里的浅层对象,对于字典、列表等类型来说,指的就是最外层的对象,也就是列表或者字典本身。可变对象的浅拷贝过程,实际上是在堆区分配了一块新内存,但是这块新内存内部的子对象,依旧是指向原内存中的位置的。

举例说明:

  1. 不可变对象的浅拷贝

    1
    2
    3
    4
    
    >>> a = 555
    >>> b = a
    >>> id(a), id(b)
    (140441428658224, 140441428658224)
    

    因为是赋值的过程,所以内存地址一致,这时如果改变b,相当于一次新的赋值,a不会一起改变

  2. 可变对象的浅拷贝

    1
    2
    3
    4
    5
    6
    7
    8
    
    >>> listA = [1, 'a', [1, 2]]
    >>> listB = listA.copy() 
    >>> print(listA, listB)
    [1, 'a', [1, 2]] [1, 'a', [1, 2]]
    >>> id(listA), id(listB)
    (140441428382344, 140441428382408)
    >>> id(listA[0]), id(listB[0])
    (10914496, 10914496)
    

    因为是拷贝后的变量listB是新开辟的内存,所以listAlistB内存地址不一致

    这时如果改变listB,情况分为两种:

    • 添加、删除或改动不可变对象,listA都不会随着改变

    • 改动可变对象,listA会一起改变,比如在listB中第二个位置,向列表[1, 2]中添加一个元素,那么listA也会改变

3.2 深拷贝

Python中的深拷贝操作,只有copy模块的 deepcopy 函数

深拷贝也可以理解成递归拷贝,在Python中,深拷贝对于子对象中的可变对象进行复制,而不可变对象则沿用之前的引用

深拷贝的最终结果就是,即使改变原来的变量,新变量也不会改变,二者互不影响

1
2
3
4
5
6
7
8
>>> import copy
>>> a = [1, 'a', [1, 2]]
>>> b = copy.deepcopy(a)
>>> print(a, b)
[1, 'a', [1, 2]] [1, 'a', [1, 2]]
>>> b[2].append(0)
>>> print(a, b)
[1, 'a', [1, 2]] [1, 'a', [1, 2, 0]]

4. 传值还是传引用?

在函数传参时,我们都知道基本的参数传递机制有两种,传值以及传引用。我们先梳理一下传值以及传引用的异同,然后再分析python中是传值还是传引用。

首先回顾一下形参与实参的区别,形参是指被调用的函数的形式参数,其作为函数内部的局部变量。实参是指调用函数时,实际传递的参数。函数会在其对应的堆栈中开辟一块空间,来存放传递进来的实参的值。在这个过程中,值传递的特点是,对于形参的任何操作,都不会改变传递进来的实参的值。而引用传递,传递的是实参的引用,也可以理解为地址,即形参和实参指向同一个地址,这时改变形参,实参的值也会发生改变

对于python中,前面我们分析了,由于其动态语言的特性,python中的变量都是引用,所以调用函数时,传递的都是引用!但是如果我们从改变形参实参是否发生改变的角度来分析,python中存在可变和不可变对象,所以对于不可变对象,形参改变,相当于重新开辟了空间以及再次进行引用的传递,并不会改变实参,相当于传值。对于可变对象,形参改变,指向的原对象也会改变,所以相当于传引用!

总结一下:

  • 从传递参数自身的性质来分析,python属于传引用

  • 从形参与实参的关系来分析,python中不可变对象是传值,可变对象是传引用

5. 总结

  1. 不可变对象在赋值时会开辟新空间

  2. 可变对象进行赋值前后,改变一个,另外一个也会改变

  3. 深浅拷贝对于不可变对象进行拷贝时,不开辟新空间,相当于赋值操作

  4. 浅拷贝拷贝的是浅层对象,深拷贝才会递归拷贝所有子对象(只针对可变对象才进行新建操作,直到最后所有对象都是不可变为止)

  5. python默认使用的是浅拷贝,因为其速度快、占用空间小、效率高

  6. python中的传值还是传引用,要辩解地分析,可变与不可变对象,其传递过程的本质不同。

6. 参考

https://mp.weixin.qq.com/s/VVKq40A4H6u4gFC1yoMB_w

Python FAQ1:传值,还是传引用?