• 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
  • 相关阅读:
    176. Second Highest Salary
    175. Combine Two Tables
    172. Factorial Trailing Zeroes
    171. Excel Sheet Column Number
    169. Majority Element
    168. Excel Sheet Column Title
    167. Two Sum II
    160. Intersection of Two Linked Lists
    个人博客记录
    <meta>标签
  • 原文地址:https://www.cnblogs.com/dongliping/p/11378823.html
Copyright © 2020-2023  润新知