Python 直接赋值、深拷贝、浅拷贝的详解

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zsq8187/article/details/109907066


正在学习 Python,根据自己的查阅与理解测试,花了一天的时间写了拷贝的笔记,想了想,先把这篇笔记单独放出来吧)

浅拷贝与深拷贝

Python 中对象的赋值是直接通过传递引用进行的。需要进行拷贝则需使用标准库中的 copy 模块。

  • 直接赋值:直接传递内存地址
  • 浅拷贝 copy.copy()创建新对象,里面复制所有元素的内存地址
  • 深拷贝 copy.deepcopy()创建新对象,不可变的复制内存地址,含可变的就递归创建新对象并复制内容(元组的类型看待见下方)
    • 在拷贝这里,可以暂且将含可变元素的元组看成可变的。
  • 不可变类型:Number(数字)、String(字符串)、Tuple(元组)【除元组外都是容器类型】
  • 可变类型:List(列表)、Dictionary(字典)、Set(集合)【斗士容器类型】
  • 非容器类型:Number(数字)、String(字符串)【都是不可变类型】
  • 容器类型:List(列表)、Dictionary(字典)、Tuple(元组)【除元组外都是可变类型】
  • 方便理解,可以先看一下后面的从别人那转载的示意图。

不可变类型的“拷贝”

不可变类型除元组外都是非容器类型,它们没有拷贝一说,使用 copy 模块进行拷贝都是直接赋值,传递内存地址。

  • 理解拷贝的一些设计原因,首先要解这个直接赋值而实现“拷贝”的原因
  • 因为拷贝的需求就是希望两个变量互不干扰,要新的一份。而不可变类型的“修改”就已经实现了两者的独立性(它的“修改”都是重新创建一个变量,将新变量指向新对象,旧变量依旧指向旧对象)。因此也就没必要再为相同内容复制开辟新空间,直接指向同一个地址即可。

直接赋值

  • 图中的验证:
a = "a"
b = a # 直接赋值,进行“拷贝”
print(b is a)
b = "b" # “修改”又是直接赋值了,因为它们无法修改
print(a)
print(b)
print(b is a)

True
a
b
False

  • 不可变类型的元组也是直接赋值。但是含有可变元素的元组特殊,见下一小节。

—> 所以在下面的可变类型/容器类型的拷贝中,有关不可变类型的子元素,就直接赋值传递内存地址即可。

要注意的是,这里直接赋值是将新旧两个变量标签直接指向同一个对象。“修改”是针对一个变量标签“修改”,没有动到另一个变量标签。从而两者独立不影响。


可变类型的直接赋值

上面是不可变类型的直接赋值,这里来看看可变类型的直接赋值。这个就是变量标签指向同一个对象。一直都是操作同一个对象,不会达到拷贝的需求:
可变类型的直接赋值

(图片不想编辑了,意思懂了就行,容器类型除了元组都是可变类型,元组的“拷贝”会单独讲)


可变类型的深拷贝与浅拷贝

它的深浅拷贝均是【创建一个新对象】,但是直接子元素的指向有所不同。这里是将新旧两个变量标签指向了不同的两个对象!(元组特殊,后面注意)

  • 所以日后对这两个变量的修改是互不干扰的!这也可以从上面的“不可变类型的直接赋值”图解简易类推:变量 AB 替换成对象 AB(它们一一对应嘛);这两个对象是容器,指向很多“字符串”。
import copy
aList = ["你", "好", 1, [1, 2, 3]]
bList = copy.copy(aList) # 浅拷贝:拷贝了所有元素的内存地址到一个新list对象里
cList = copy.deepcopy(aList) # 深拷贝:在浅拷贝的基础上,还递归为容器元素创建新对象拷贝内容

在直接赋值里,我们以新旧两个变量标签为基准,来看变量内容的修改。而这里,我们继续看变量标签,它指向了新旧两个容器对象。当容器对象的元素为不可变类型时,遵循上述的逻辑,新建对象赋予容器对象的元素新的地址从而互不影响。

  1. 浅拷贝中,因为不可变类型 "你""好"1 修改都是新建对象赋予新地址,从而两者互不干扰,但是修改子列表 [1, 2, 3] 不会创建新对象,因此两者这个一直都是引用同一个,互有影响。
    在这里插入图片描述
  • 字典中的浅拷贝:创建两个字典对象,对键创建了新的对象*,但是复制了值的内存地址,它们值是共享的(需要了解字典的内存示意图)。如果值有可变类型,那么依旧互有影响。
    • 对键创建了新的对象:例如字典 A,B:B 是 A 的浅拷贝,此时如果删除 B 的一个键,那么 A 中对应的键是没有影响的。实现了两个字典的这种初步独立,实现了这种浅拷贝的需求。
    • 详细图解见第 4 点,建议先看完 1、2 点。
  1. 深拷贝中,进行了递归,为容器元素创建了新对象拷贝内容。从而实现两个变量永远互不干扰。
    在这里插入图片描述
  • 字典中的深拷贝:对键、值都创建了新的对象(不可变类型的依旧只是复制内存地址),两者互不干扰。
  1. 特殊情况:元组
  • 元组是不可变类型,应该是无法拷贝的,是传递内存地址
  • 特殊的是含可变类型元素的元组可以进行深拷贝,新建一个对象,并且递归为可变元素创建新对象(它依旧没有浅拷贝,copy.copy() 也是传递内存地址
  1. 理解了上面列表的深浅拷贝后,再类推理解字典的深浅拷贝也就不难了。

字典的浅拷贝字典的深拷贝

  1. 有关上述的测试:略。知识点别忘了就好。。

转载资料

  1. 直接赋值:传递内存地址
    直接赋值

  2. 浅拷贝:创建新对象,复制所有内存地址
    浅拷贝

  3. 深拷贝:创建新对象,复制不可变类型的内存地址,递归可变类型创建新对象。
    深拷贝!

  4. 不可变元组 和 可变列表的深拷贝,浅拷贝区别

不可变元组 和 可变列表的深拷贝,浅拷贝区别
这里,第一部分,全不可变的元组是直接赋值,所以都是 True
第二部分,拷贝可变类型,浅拷贝和深拷贝它们的内容是一致的所 ==True;而它们拷贝都是新建对象,所以内存地址 ==False

拷贝的使用处

Python 一般默认使用的是浅拷贝。以下例子等待继续学习补充。
比如:

  • 切片的 [:] 使用的是浅拷贝
  • 关键字参数:**extra 使用了浅拷贝,函数内部删除键对外部原有值无影响,但可变类型的值是共用的

参考/图片转载: 图解Python深拷贝和浅拷贝 - 田小计划 - 博客园

猜你喜欢

转载自blog.csdn.net/zsq8187/article/details/109907066