[ python ] 深浅拷贝

首先要了解什么是拷贝、浅拷贝、深拷贝?

拷贝:

    从原始数据复制一份出来,当复制成功后,这两份数据都是相互独立的,即修改任意一份数据都不会影响另一份数据。

浅拷贝:

    python中,浅拷贝就是只是拷贝最外层的类型,简单来讲就是拷贝了引用,并没有拷贝内容. copy.copy()

深拷贝:

    对于一个对象所有层次的拷贝(递归拷贝)copy.deepcopy()

要知道深浅拷贝的区别,首先要知道python中什么是 可变数据类型 和 不可变数据类型

不可变数据类型的定义:

    python中的不可变数据类型,不允许变量的值发生变化,如果改变了变量的值,相当于是新建了一个对象,而对于相同的值的对象,在内存中则只有一个对象,内部会有一个引用计数来记录有多少个变量引用这个对象.

    python中 不可变数据类型:

  •   整型
  •   浮点数
  •   布尔值
  •   字符串
  •   元组

可变数据类型的定义:

    可变数据类型,允许变量的值发生变化,即如果对变量进行append、+=等这种操作后,只是改变了变量的值,而不会新建一个对象,变量引用的对象的地址也不会变化,不过对于相同的值的不同对象,在内存中则会存在不同的对象,即每个对象都有自己的地址, 相当于内存中对于同值的对象保存了多份,这里不存在引用计数,是实实在在的对象。

 python中 可变数据类型:

  •   列表
  •   字典

通过python中数据类型的分类,我们谈论以下几种的拷贝:

不可变数据类型:

  •     赋值
  •     浅拷贝
  •     深拷贝


可变数据类型:

  •     赋值
  •     浅拷贝
  •     深拷贝

不可变数据类型

1. 赋值

我们已知python中不可变数据类型:整型、浮点数、字符串、布尔值、元组

In [1]: a1 = 123    # 整型

In [2]: a2 = a1

In [3]: id(a1), id(a2)
Out[3]: (1562980880, 1562980880)

In [4]: b1 = 1.123    # 浮点数

In [5]: b2 = b1

In [6]: id(b1), id(b2)
Out[6]: (1503028953024, 1503028953024)

In [7]: c1 = 'hello'    # 字符串

In [8]: c2 = c1

In [9]: id(c1), id(c2)
Out[9]: (1503040484272, 1503040484272)

In [10]: d1 = True    # 布尔值

In [11]: d2 = d1

In [12]: id(d1), id(d2)
Out[12]: (1562722720, 1562722720)

In [13]: e1 = (1, 2, 3, 'hkey')    # 元组

In [14]: e2 = e1

In [15]: id(e1), id(e2)
Out[15]: (1503040349032, 1503040349032)

通过以上的例子,a1、a2 赋值的值是一样的。因为python有一个重用机制,对于 不可变数据类型 来说,python并不会开辟一块新的内存空间,而是维护同一块内存地址,只是将 不可变数据类型 对应的地址引用赋值给变量a1、a2。所以根据输出结果,a1和a2其实对应的是同一块内存地址,只是两个不同的引用。

结论:对于 不可变数据类型 通过'='赋值,不可变数据类型在内存当中用的都是同一块地址。

2. 浅拷贝

In [1]: import copy

In [2]: a1 = 3.14

In [3]: a2 = copy.copy(a1)

In [4]: id(a1), id(a2)
Out[4]: (1690132070168, 1690132070168)

通过使用copy模块里的copy()函数来进行浅拷贝,把a1拷贝一份赋值给a2,查看输出结果发现,a1和a2的内存地址还是一样。

结论:对于 不可变数据类型 通过浅拷贝,不可变数据类型在内存当中用的都是同一块地址。

3. 深拷贝

In [1]: import copy

In [2]: a1 = 'hello'

In [3]: a2 = copy.deepcopy(a1)

In [4]: id(a1), id(a2)
Out[4]: (1645307287064, 1645307287064)

通过使用copy模块里的deepcopy()函数来进行深拷贝,把a1拷贝一份赋值给a2,查看输出结果发现,a1和a2的内存地址还是一样。

结论:对于 不可变数据类型 通过深拷贝,不可变数据类型在内存当中用的都是同一块地址。

这里可以对 不可变数据类型 下一个定义了:
    对于python中 不可变数据类型 的赋值、浅拷贝、深拷贝在内存中都是指向同一内存地址。如下图:

可变数据类型

1. 赋值

In [1]: n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]}

In [2]: n2 = n1

In [3]: id(n1), id(n2)
Out[3]: (1888346752712, 1888346752712)

在上面的例子中,我们使用了列表嵌套列表的方式,赋值后内存空间地址是一致的。其中原理如下图:

结论:对于 可变数据类型 进行赋值内存地址是不会变化的。

2. 浅拷贝

import copy
n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]}
n3 = copy.copy(n1)  # 浅拷贝
print("第一层字典的内存地址:")
print(id(n1))
print(id(n3))
print("第二层嵌套的列表的内存地址:")
print(id(n1["k3"]))
print(id(n3["k3"]))


执行结果:

第一层字典的内存地址:
2260623665800
2260625794440
第二层嵌套的列表的内存地址:
2260626131144
2260626131144

通过以上结果可以看出,进行浅拷贝时,我们的字典第一层n1和n3指向的内存地址已经改变了,但是对于第二层里的列表并没有拷贝,它的内存地址还是一样的。原理如下图:

结论:对于 python 可变数据类型,浅拷贝只能拷贝第一层地址。

3. 深拷贝

import copy

n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]}
n4 = copy.deepcopy(n1)  # 深拷贝
print("第一层字典的内存地址:")
print(id(n1))
print(id(n4))
print("第二层嵌套的列表的内存地址:")
print(id(n1["k3"]))
print(id(n4["k3"]))

执行结果:

第一层字典的内存地址:
2018569398920
2018574527176
第二层嵌套的列表的内存地址:
2018576058568
2018576056840

通过以上结果发现,进行深拷贝时,字典里面的第一层和里面嵌套的地址都已经变了。对于深拷贝,它会拷贝多层,将第二层的列表也拷贝一份,
如果还有第三层嵌套,那么第三层的也会拷贝,但是对于里面的最小元素,比如数字和字符串,这里就是“wu”,123,“alex”,678之类的,
按照python的机制,它们会共同指向同一个位置,它的内存地址是不会变的。原理如下图:

结论:对于 python 可变数据类型,它里面嵌套多少层,就会拷贝多少层出来,但是最底层的数字和字符串不变。

python 深浅拷贝的实例:

我们在维护服务器信息的时候,经常会要更新服务器信息,这时我们重新一个一个添加是比较麻烦的,我们可以把原数据类型拷贝一份,在它的基础上做修改。

实例1:使用浅拷贝

import copy

# 定义了一个字典,存储服务器信息
dic = {
    'cpu':[80, ],
    'mem':[80, ],
    'disk':[80, ]
}

print('before', dic)
new_dic = copy.copy(dic)
new_dic['cpu'][0] = 50 # 更新 cpu 为 50
print(dic)
print(new_dic)


执行结果:

before {'cpu': [80], 'mem': [80], 'disk': [80]}
{'cpu': [50], 'mem': [80], 'disk': [80]}
{'cpu': [50], 'mem': [80], 'disk': [80]}

这时我们会发现,使用浅拷贝时,我们修改新的字典的值之后,原来的字典里面的cpu值也被修改了,这并不是我们希望看到的。

实例2:使用深拷贝

import copy

# 定义了一个字典,存储服务器信息
dic = {
    'cpu':[80, ],
    'mem':[80, ],
    'disk':[80, ]
}

print('before', dic)
new_dic = copy.deepcopy(dic)
new_dic['cpu'][0] = 50 # 更新 cpu 为 50
print(dic)
print(new_dic)


执行结果:

before {'cpu': [80], 'disk': [80], 'mem': [80]}
{'cpu': [80], 'disk': [80], 'mem': [80]}
{'cpu': [50], 'disk': [80], 'mem': [80]}

使用深拷贝的时候,发现只有新的字典的cpu值被修改了,原来的字典里面的cpu值没有变。大功告成!

原文地址:https://www.cnblogs.com/hukey/p/9812035.html