zoukankan      html  css  js  c++  java
  • Python 深浅拷贝

    概念普及:对象、可变类型、引用

    数据拷贝会涉及到Python中对象、可变类型、引用这3个概念,先来看看这几个概念,只有明白了他们才能更好的理解深拷贝与浅拷贝到底是怎么一回事。

    Python对象

    在Python中,对对象有一种很通俗的说法,万物皆对象。说的就是构造的任何数据类型都是一个对象,无论是数字,字符串,还是函数,甚至是模块,Python都对当做对象处理。

    所有Python对象都拥有三个属性:身份、类型、值。


    可变与不可变对象

    在Python中,按更新对象的方式,可以将对象分为2大类:可变对象与不可变对象。

     可变对象:  列表、字典、集合

      所谓可变是指可变对象的值可变,身份是不变的。

     不可变对象:数字、字符串、元组

      不可变对象就是对象的身份和值都不可变。新创建的对象被关联到原来的变量名,旧对象被丢弃,垃圾回收器会在适当的时机回收这些对象。


    引用

    在 Python 程序中,每个对象都会在内存中申请开辟一块空间来保存该对象,该对象在内存中所在位置的地址被称为引用。在开发程序时,所定义的变量名实际就对象的地址引用。

    引用实际就是内存中的一个数字地址编号,在使用对象时,只要知道这个对象的地址,就可以操作这个对象,但是因为这个数字地址不方便在开发时使用和记忆,所以使用变量名的形式来代替对象的数字地址。 在 Python 中,变量就是地址的一种表示形式,并不开辟开辟存储空间。

    就像 IP 地址,在访问网站时,实际都是通过 IP 地址来确定主机,而 IP 地址不方便记忆,所以使用域名来代替 IP 地址,在使用域名访问网站时,域名被解析成 IP 地址来使用。

    通过一个例子来说明变量和变量指向的引用就是一个东西

    In [11]: age = 18
    
    In [12]: id(age)
    Out[12]: 1730306752
    
    In [13]: id(18)
    Out[13]: 1730306752

    逐步深入:引用赋值

    上边已经明白,引用就是对象在内存中的数字地址编号,变量就是方便对引用的表示而出现的,变量指向的就是此引用。赋值的本质就是让多个变量同时引用同一个对象的地址。  那么在对数据修改时会发生什么问题呢?

    • 不可变对象的引用赋值

      对不可变对象赋值,实际就是在内存中开辟一片空间指向新的对象,原不可变对象不会被修改。

    下面通过案例来理解一下:

    a与b在内存中都是指向1的引用,所以a、b的引用是相同的

    In [1]: a = 1

    In [2]: b = a

    In [3]: id(a)
    Out[3]: 1730306496

    In [4]: id(b)
    Out[4]: 1730306496

    现在再给a重新赋值,看看会发生什么变化?

    从下面不难看出:当给a 赋新的对象时,将指向现在的引用,不在指向旧的对象引用。

    In [1]: a = 1

    In [2]: b = a

    In [5]: a = 2

    In [6]: id(a)
    Out[6]: 1730306816

    In [7]: id(b)
    Out[7]: 1730306496

    • 可变对象的引用赋值

      可变对象保存的并不是真正的对象数据,而是对象的引用。当对可变对象进行赋值时,只是将可变对象中保存的引用指向了新的对象。

    仍然通过一个实例来体会一下,可变对象引用赋值的过程。

    当改变l1时,整个列表的引用会指新的对象,但是l1与l2都是指向保存的同一个列表的引用,所以引用地址不会变。

    In [3]: l1 = [1, 2, 3]

    In [4]: l2 = l1

    In [5]: id(l1)
    Out[5]: 1916633584008

    In [6]: id(l2)
    Out[6]: 1916633584008

    In [7]: l1[0] = 11

    In [8]: id(l1)
    Out[8]: 1916633584008

    In [9]: id(l2)
    Out[9]: 1916633584008

    浅拷贝

     1 >>> will = ["Will", 28, ["Python", "C#", "JavaScript"]]
     2 >>> willer=will
     3 >>> id(will)
     4 47459592
     5 >>> id(willer)
     6 47459592
     7 >>> [id(ele) for ele in will]
     8 [50904304, 8791323210848, 47459784]
     9 >>> [id(ele) for ele in willer]
    10 [50904304, 8791323210848, 47459784]
    11 >>> will[0]='willer'
    12 >>> will[2].append("CSS")
    13 >>> will
    14 ['willer', 28, ['Python', 'C#', 'JavaScript', 'CSS']]
    15 >>> willer
    16 ['willer', 28, ['Python', 'C#', 'JavaScript', 'CSS']]
    17 >>> [id(ele) for ele in will]
    18 [50500848, 8791323210848, 47459784]
    19 >>> [id(ele) for ele in willer]
    20 [50500848, 8791323210848, 47459784]

    通过分析代码

    • 首先,依然创建了一个will变量,指向一个list类型的对象
    • 然后,通过copy模块里面的浅拷贝函数copy(),对will指向的对象进行浅拷贝,然后浅拷贝生成的新对象赋值给wilber变量
      • 浅拷贝会创建一个新的对象,这个例子中"wilber is not will"
      • 但是,对于对象中的元素,浅拷贝就只会使用原始元素的引用(内存地址),也就是说"wilber[i] is will[i]"
    • 当对will进行修改的时候
      • 由于list的第一个元素是不可变类型,所以will对应的list的第一个元素会使用一个新的对象39758496
      • 但是list的第三个元素是一个可变类型,修改操作不会产生新的对象,所以will的修改结果会相应的反应到wilber上

    总结一下,当我们使用下面的操作的时候,会产生浅拷贝的效果:

    • 使用切片[:]操作
    • 使用工厂函数(如list/dir/set)
    • 使用copy模块中的copy()函数

    深拷贝

     1 >>> import copy
     2 >>> will = ["Will", 28, ["Python", "C#", "JavaScript"]]
     3 >>> wilber = copy.deepcopy(will)
     4 >>> id(will)
     5 47327624
     6 >>> id(wilber)
     7 47328200
     8 >>> [id(ele) for ele in will]
     9 [52508272, 8791323210848, 47327432]
    10 >>> [id(ele) for ele in wilber]
    11 [52508272, 8791323210848, 47328008]
    12 >>> will[0] = "Wilber"
    13 >>> will[2].append("CSS")
    14 >>> id(will)
    15 47327624
    16 >>> id(wilber)
    17 47328200
    18 >>> [id(ele) for ele in will]
    19 [52558128, 8791323210848, 47327432]
    20 >>> [id(ele) for ele in wilber]
    21 [52508272, 8791323210848, 47328008]
    22 >>> will
    23 ['Wilber', 28, ['Python', 'C#', 'JavaScript', 'CSS']]
    24 >>> wilber
    25 ['Will', 28, ['Python', 'C#', 'JavaScript']]

    通过对代码分析

    • 首先,同样使用一个will变量,指向一个list类型的对象
    • 然后,通过copy模块里面的深拷贝函数deepcopy(),对will指向的对象进行深拷贝,然后深拷贝生成的新对象赋值给wilber变量
      • 跟浅拷贝类似,深拷贝也会创建一个新的对象,这个例子中"wilber is not will"
      • 但是,对于对象中的元素,深拷贝都会重新生成一份(有特殊情况,下面会说明),而不是简单的使用原始元素的引用(内存地址)
        • 例子中will的第三个元素指向39737304,而wilber的第三个元素是一个全新的对象39773088,也就是说,"wilber[2] is not will[2]"
    • 当对will进行修改的时候
      • 由于list的第一个元素是不可变类型,所以will对应的list的第一个元素会使用一个新的对象39758496
      • 但是list的第三个元素是一个可不类型,修改操作不会产生新的对象,但是由于"wilber[2] is not will[2]",所以will的修改不会影响wilber
  • 相关阅读:
    java类型转换
    JVM内存各个区域分工简单介绍
    用数组实现栈
    一些关于Spring的随笔
    设计模式学习笔记(三)之静(动)态代理模式、适配器模式
    浅谈经典排序算法
    PetStore项目总结
    设计模式学习笔记(二)之观察者模式、装饰者模式
    Spring的校验(Validator)
    设计模式学习笔记(一)之工厂模式、单例模式
  • 原文地址:https://www.cnblogs.com/dongliping/p/11378823.html
Copyright © 2011-2022 走看看