专栏首页Python 知识大全Python中浅拷贝与深拷贝

Python中浅拷贝与深拷贝

阅读本文需要5.5分钟

Python中的赋值语句没有创建副本对于对象来说,它们只是将名称绑定到对象。对于不可变的对象来说,通常是没有什么区别的。但是,为了处理可变对象或可变对象的集合,我们可能需要一种方法来创建这些对象的“真实副本“。

在本文中,将介绍如何在Python 3中复制或“克隆”对象,以及所涉及的一些注意事项。

注:本教程是用Python 3编写的,但是在复制对象时,Python 2和3并没有什么区别。当有不同时,会在文中指出。

让我们首先看看如何复制Python的内置集合。Python内置的集合是可变的,如列表、数据集和集合都可以通过在现有集合上调用它们的原来函数进行复制:

new_list = list(original_list)
new_dict = dict(original_dict)
new_set = set(original_set)

但是,此方法不适用于自定义对象,而且在此基础上,它只创建浅拷贝...对于复合对象,如列表、数据集和集合,有一个重要的区别:浅拷贝和深拷贝

  • A 浅拷贝意味着构建一个新的集合对象,然后用对原始集合中的子对象引用填充它。本质上,一个浅拷贝只是一个层次的深度。复制过程不会递归,因此不会创建子对象本身的副本。
  • A 深拷贝使复制过程递归。这意味着首先构造一个新的集合对象,然后使用递归在原始集合中找到的子对象的副本来填充它。以这种方式复制一个对象会遍历整个对象树,从而创建一个完全独立的原对象及其所有子对象的克隆。

让我们来看看一些例子来证明深拷贝和浅拷贝之间的区别。

浅拷贝

在下面的示例中,我们将创建一个新的嵌套列表,然后将其复制到list()中:

>>> xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> ys = list(xs)  # 开始浅拷贝

这意味着ys将成为一个新的、独立的对象,我们可以通过检查这两个对象来验证这一点:

>>> xs
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> ys
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

确认ys是独立对象,让我们设计一个小实验。可以尝试将一个新的子列表添加到原始(xs),然后检查确保此修改不影响副本(ys):

>>> xs.append(['new sublist'])
>>> xs
[[1, 2, 3], [4, 5, 6], [7, 8, 9], ['new sublist']]
>>> ys
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

正如所看到的,达到了预期的效果。

但是,因为我们只创建了一个浅层的副本,ys中存储的原始子对象引用xs.

这些没有复制,只是在复制的列表中再次被引用。

因此,当在xs中,此修改反映在ys也是一样,那是因为两个列表共享相同的子对象。

>>> xs[1][0] = 'X'
>>> xs
[[1, 2, 3], ['X', 5, 6], [7, 8, 9], ['new sublist']]
>>> ys
[[1, 2, 3], ['X', 5, 6], [7, 8, 9]]

如果我们创建了一个深拷贝xs,在第一步中,这两个对象将完全独立。这是物体的浅拷贝和深拷贝之间的实际区别。

现在知道了如何创建一些内置集合类的浅拷贝,并且了解了浅拷贝和深拷贝之间的区别。但是我们仍然希望得到答案是:

  • 如何创建内置集合的深拷贝?
  • 如何创建任意对象(包括自定义类)的副本?

这些问题的答案在Python标准库中的copy模块里。该模块为创建任意Python对象的浅拷贝和深拷贝提供了一个简单的接口。

深拷贝

让我们重复前面的列表复制示例,但有一个重要的区别。这次我们要使用deepcopy()这个方法创建一个列表。

>>> import copy
>>> xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> zs = copy.deepcopy(xs)

当检查xs及其克隆zs我们用了copy.deepcopy(),将看到它们看起来是相同的--就像前面的示例:

>>> xs
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> zs
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

但是,如果对原始对象中的一个子对象进行了修改(xs),将看到此修改不会影响深层副本(zs).

这一次,两个对象,原件和副本都是完全独立的。xs是递归地克隆的,包括它的所有子对象:

>>> xs[1][0] = 'X'
>>> xs
[[1, 2, 3], ['X', 5, 6], [7, 8, 9]]
>>> zs
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

当你亲身体验这些例子的时候,理解起来就更容易了。

顺便说一句,还可以使用copy模块,copy.copy()函数创建对象的浅副本。

如果需要清楚地表达你正在代码中的某个地方创建一个浅表副本,这个方法非常有用的。

复制任意Python对象

现在我们需要回答的问题是如何创建任意对象(包括自定义类)的副本(浅的和深的),现在让我们看看这个。copy.copy()copy.deepcopy()函数可用于复制任何对象。我将基于前面的列表举个简单的例子。首先让我们定义一个简单的类:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'Point({self.x!r}, {self.y!r})'

我加了一个__repr__()实现,这样我们就可以在Python解释器中轻松地检查从该类中创建的对象。

注:上面的示例使用Python3.6f-string构造由__repr__...在Python 2和3.6之前的Python 3版本中,将使用不同的字符串格式表达式,例如:

def __repr__(self):
    return 'Point(%r, %r)' % (self.x, self.y)

接下来,我们将创建一个Point实例,使用copy模块浅复制它:

>>> a = Point(23, 42)
>>> b = copy.copy(a)

如果我们检查原件的内容Point对象及其克隆,看到了我们所期望的:

>>> a
Point(23, 42)
>>> b
Point(23, 42)
>>> a is b
False

我们来看一个更复杂的例子。定义另一个类来表示二维矩形:

class Rectangle:
    def __init__(self, topleft, bottomright):
        self.topleft = topleft
        self.bottomright = bottomright

    def __repr__(self):
        return (f'Rectangle({self.topleft!r}, '
                f'{self.bottomright!r})')

同样,首先我们将尝试创建矩形实例的浅表副本:

rect = Rectangle(Point(0, 1), Point(5, 6))
srect = copy.copy(rect)

如果检查原始矩形及其副本,将看到__repr__()正在进行覆盖,浅层复制过程:

>>> rect
Rectangle(Point(0, 1), Point(5, 6))
>>> srect
Rectangle(Point(0, 1), Point(5, 6))
>>> rect is srect
False

还记得前面的列表示例是如何说明深拷贝和浅拷贝之间的区别的吗?我要用同样的方法,在对象层次结构中更深地修改一个对象,然后将在(浅)副本中更改:

>>> rect.topleft.x = 999
>>> rect
Rectangle(Point(999, 1), Point(5, 6))
>>> srect
Rectangle(Point(999, 1), Point(5, 6))

接下来,我将创建原始矩形的深拷贝。然后,我修改一下:

>>> drect = copy.deepcopy(srect)
>>> drect.topleft.x = 222
>>> drect
Rectangle(Point(222, 1), Point(5, 6))
>>> rect
Rectangle(Point(999, 1), Point(5, 6))
>>> srect
Rectangle(Point(999, 1), Point(5, 6))

这一次,深拷贝(drect)完全独立于原始(rect)和浅拷贝(srect).

总结

  • 创建对象的浅拷贝不会复制子对象。因此,副本并不完全独立于原件。
  • 对象的深拷贝将递归地复制子对象。克隆完全独立于原始副本,但是创建深拷贝要慢一些。
  • 类复制任意对象(包括自定义类)。

本文分享自微信公众号 - Python 知识大全(TuoLaJi522),作者:杨大厅长

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-12-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • python数据可视化——词云

    词云百度百科:“词云”就是对网络文本中出现频率较高的“关键词”予以视觉上的突出,形成“关键词云层”或“关键词渲染”,从而过滤掉大量的文本信息,使浏览网页者只要一...

    Python知识大全
  • Python 文件I/O

    Python提供了必要的函数和方法进行默认情况下的文件基本操作。你可以用file对象做大部分的文件操作。

    Python知识大全
  • sqrt是什么函数

    注意:sqrt()是不能直接访问的,需要导入 math 模块,通过静态对象调用该方法。

    Python知识大全
  • 基于贝叶斯算法的文本分类算法

    1、基本定义: 分类是把一个事物分到某个类别中。一个事物具有很多属性,把它的众多属性看作一个向量,即x=(x1,x2,x3,…,xn),用x这个向量来代表这个...

    机器学习AI算法工程
  • salesforce零基础学习(八十七)Apex 中Picklist类型通过Control 字段值获取Dependent List 值

    注:本篇解决方案内容实现转自:http://mysalesforceescapade.blogspot.com/2015/03/getting-dependen...

    用户1169343
  • salesforce零基础学习(八十七)Apex 中Picklist类型通过Control 字段值获取Dependent List 值

    注:本篇解决方案内容实现转自:http://mysalesforceescapade.blogspot.com/2015/03/getting-dependen...

    用户1169343
  • ARC内存管理中容易忽略的问题

    看到好几篇文章都在说这道面试题,字符串差不多是每个高级语言必有的,在实际项目中也的确是使用的最多类型之一。本文就以此题开始我们的内存管理的讨论。

    羊羽shine
  • linux内核写时复制机制源代码解读

    韩传华,就职于国内一家半导体公司,主要从事linux相关系统软件开发工作,负责Soc芯片BringUp及系统软件开发,乐于分享喜欢学习,喜欢专研Linux内核源...

    Linux阅码场
  • TP5 验证码功能实现

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011415782/article/de...

    泥豆芽儿 MT
  • ofo拒绝滴滴收购邀约?共享单车真的能战斗到底吗?

    孟永辉

扫码关注云+社区

领取腾讯云代金券