首页 > 编程语言 >【Python】python深拷贝和浅拷贝(二)

【Python】python深拷贝和浅拷贝(二)

时间:2023-01-28 16:01:41浏览次数:59  
标签:__ Python self python dict 拷贝 copy id

【Python】python深拷贝和浅拷贝(二)

大家好,我们的gzh是朝阳三只大明白,满满全是干货,分享近期的学习知识以及个人总结(包括读研和IT),跪求一波关注,希望和大家一起努力、进步!!

前言

上一期我们介绍了Python中深拷贝和浅拷贝的定义以及它们在执行过程中内存结构,同时也给出了深拷贝和浅拷贝的方法。(没有看上一期的朋友看这里,)这一期我们将介绍一下在Python中自定义类型该如何控制深拷贝和前拷贝的行为。

自定义浅拷贝行为

上一期我们介绍了进行一个对象的潜拷贝需要调用copy库中的​​copy​​方法。在代码底层会经历5个步骤:

  1. 获取对象的类,这一点可以理解为调用​​__class__​​魔术方法;
  2. 在已有的【_copy_dispatch】中获取现有的copier;
  3. 如果在【_copy_dispatch】中没有现有的copier,那么先判断他是否是元类(MetaClass)创建的对象,即类(Class),如果是则调用​​_copy_immutable​​方法,处理方式和不可变对象一致;
  4. 如果上述copier都是None,则检查对象的​​__copy__​​方法,如果存在该方法则调用该方法;
  5. 使用序列化的方式进行拷贝...

现在定义一个空类并调用​​copy​​方法,看看会发生什么:

class A:
def __init__(self) -> None:
self.product_list = [1, 2, 3, 4]
self.dict = {1: 'a', 2: 'b'}

a = A()
a_copier = copy(a)
print(id(a.list), id(a_copier.list))
print(id(a.dict), id(a_copier.dict))

【Python】python深拷贝和浅拷贝(二)_Python入门

从上图可以看到,如果没有定义相关拷贝方法,使用​​copy​​方法对自定义类的对象进行拷贝将会直接执行步骤5。

【Python】python深拷贝和浅拷贝(二)_Python_02

从程序的运行结果可以看到复制的对象和原始对象的内存位置不同,但是内部元素地址都是相同的,也就是说如果不实现​​__copy__​​魔术方法,那么他的行为和其他内置对象例如列表的浅拷贝行为一致,这种策略符合了python对浅拷贝的定义。

浅拷贝:构造一个新的对象,尽可能的将原始对象中的所有找到的对象引用加入到新构造的对象中;

如果想要控制对象浅拷贝的行为,需要在类中实现​​__copy__​​魔术方法,现在重新写一个类:

class A(object):
def __init__(self):
self.list = [1, 2, 3, 4]
self.set = {1, 2, 3, 4}
self.dict = {1: 'a', 2: 'b', 3: 'c'}

def __copy__(self):
print("进行浅拷贝")
cls = self.__class__
result = cls.__new__(cls)
result.__dict__ = {k: v for k, v in self.__dict__.items()}

return result

a = A()
a_copied = copy(a)
print(id(a), id(a_copied))
print(id(a.list), id(a_copied.list))
print(id(a.set), id(a_copied.set))
print(id(a.dict), id(a_copied.dict))

我们在A这个类中重写了​​__copy__​​方法,此时浅拷贝有三个步骤

  1. 通过​​__class__​​方法获取对象的类(class);
  2. 通过调用​​__new__​​方法创建了一个新对象;
  3. 将被拷贝对象的​​__dict__​​属性通过字典推导式赋予新对象的​​__dict__​​属性;

【Python】python深拷贝和浅拷贝(二)_深拷贝_03

从程序结果看,a和a_copied已经是两个对象了,但是它们内部的属性依旧是指向同一个子对象。

自定义深拷贝行为

上一期介绍到想要进行深拷贝需要调用copy库中的​​deepcopy​​方法。在代码底层和​​copy​​方法一致,只不过是从【_deepcopy_dispatch】和【dispatch_table】中获取相应的拷贝方法对象,下面的代码以【list】对象的深拷贝为例进行介绍:

def _deepcopy_list(x, memo, deepcopy=deepcopy):
y = []
memo[id(x)] = y
append = y.append
for a in x:
append(deepcopy(a, memo))
return y

列表的深拷贝就是嵌套的对列表中每一个元素进行深拷贝,同时采用memo这个字典对象进行记录。符合Python对深拷贝的定义。

深拷贝:构造一个新的对象,然后递归的在原始对象中将找到的对象的副本插入其中。

接下来将会以画图的方式以一个列表做对象进行演示

from copy import copy, deepcopy
list1 = [1, 2, [3, 4]]
list1_deepcopied = deepcopy(list1)

【Python】python深拷贝和浅拷贝(二)_Python提升_04

深拷贝完毕之后【list1】和【list1_deepcopied】的内存分布如下:

【Python】python深拷贝和浅拷贝(二)_Python入门_05

最后如果想要控制对象深拷贝的行为,需要重写​​__deepcopy__​​方法,以浅拷贝的类为例:

class A(object):
def __init__(self):
self.list = [1, 2, 3, 4]
self.set = {1, 2, 3, 4}
self.dict = {1: 'a', 2: 'b', 3: 'c'}

def __copy__(self):
print("进行浅拷贝")
cls = self.__class__
result = cls.__new__(cls)
result.__dict__ = {k: v for k, v in self.__dict__.items()}

return result

def __deepcopy__(self, memo):
print("进行深拷贝")
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
setattr(result, k, deepcopy(v, memo))
return result

a = A()
a_copied = deepcopy(a)
print(id(a), id(a_copied))
print(id(a.list), id(a_copied.list))
print(id(a.set), id(a_copied.set))
print(id(a.dict), id(a_copied.dict))

基本步骤和浅拷贝基本一致,需要注意的点如下:

  1. 定义一个memo字典放置循环引用的无限复制;
  2. 通过被拷贝对象的​​__dict__​​属性递归进行新对象的属性设置,本质还是对所有子对象都进行深拷贝。

运行结果如下,可以看到所有的子对象地址都不再相同。

【Python】python深拷贝和浅拷贝(二)_深拷贝_06

如果我们查看【list】属性的0号元素,可以发现他们的地址是相同的,具体原因可以看上一期源码分析,针对整数、字符串这种不可变类型,copier的处理方式都是直接将原始对象引用返回。

print(id(a.list[0]), id(a_copied.list[0]))

【Python】python深拷贝和浅拷贝(二)_Python_07

往期回顾

  1. ​【Python】python深拷贝和浅拷贝(一)​
  2. ​【Python】type、isinstance、issubclass详解​

文中难免会出现一些描述不当之处(尽管我已反复检查多次),欢迎在留言区指正,相关的知识点也可进行分享,希望大家都能有所收获!!

标签:__,Python,self,python,dict,拷贝,copy,id
From: https://blog.51cto.com/u_15945763/6024979

相关文章

  • python图片拼接
     首先我们来尝试将分片的图片复原为正常的图片这里是六张切成小细条的图片,原本是一张大图的,现在我们用python将他们合并到一块,题外话图片来源于中华连环画,*http://www......
  • 【Python学习002】函数参数
    我们的gzh是【朝阳三只大明白】,满满全是干货,分享近期的学习知识以及个人总结(包括读研和IT),希望大家一起努力,一起加油!求关注!!概述函数是组织好的、可重复使用的,用来实现单一,或......
  • 上古神兵,先天至宝,Win11平台安装和配置NeoVim0.8.2编辑器搭建Python3开发环境(2023最
    毫无疑问,我们生活在编辑器的最好年代,Vim是仅在Vi之下的神级编辑器,而脱胎于Vim的NeoVim则是这个时代最好的编辑器,没有之一。异步支持、更好的内存管理、更快的渲染速度、更......
  • python对接API二次开发高级实战案例解析:百度地图Web服务API封装函数(行政区划区域检索
    文章目录​​前言​​​​一、IP定位​​​​1.请求URL​​​​2.获取IP定位封装函数​​​​3.输出结果​​​​二、国内天气查询​​​​1.请求url​​​​2.天气查询封装......
  • colab在更换python包版本时,如何正确重启
    当我在使用Node2Vec这个包的时候,遇到了gem和numpy的版本冲突问题。最后在pc上测试,发现只要升级numpy就能解决问题。但是在colab中更新numpy版本依旧报错。后面才发现,就算在c......
  • Python爬虫实践代码示例
    对于刚入门爬虫的小伙伴来说,累积经验多练习代码是非常有必要的,下面就是有关爬虫的一些小案例,欢迎大家指正。importrequestsfrombs4importBeautifulSoup#importpandas......
  • Python入门之转义符
    """转义符:改变原始字符含义的特殊符号"""#不支持所见即所得name="黎二狗"name='黎二狗'#可见即所得name='''黎二狗'''name="""黎......
  • python入门之str/ord/chr
    """字符串(str):由一系列字符组成的不可变系列容器,存储的是字符的编码值。编码:1.字节byte:计算机最小存储单位,等于8位bit。(bitBKBMBGT)2.字符:单个的数......
  • Python中得可变哈希不可变哈希
    类型与哈希哈希(散列计算),可以将任意长度的输出,通过散列算法变为固定长度输出,简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。​​1.可哈希......
  • Python字符串
    1.replace替换#replace实现字符串替换a='sjbfnjajjkdgbnv'a=a.replace('f','里')print(a)out:sjb里njajjkdgbnv整个过程,实际是创建了新的字符串对象,并指向了变量a,并......