Python 对象的浅复制与深复制

2023-10-14

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 深拷贝进行复制过程递归的。这意味着首先构造一个新的集合对象,然后用原始集合中找到的子对象的副本递归地填充它。以这种方式复制对象会遍历整个对象树,以创建原始对象及其所有子对象的完全独立的克隆。

我知道,这有点拗口。因此,让我们看一些示例来了解深拷贝和浅拷贝之间的差异。

免费下载: 从 Python Tricks: The Book 中获取示例章节它通过简单的示例向您展示了 Python 的最佳实践,您可以立即应用这些示例来编写更漂亮的 Python 代码。

制作浅副本

在下面的示例中,我们将创建一个新的嵌套列表,然后浅浅地复制它与list()工厂功能:

>>>
>>> xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> ys = list(xs)  # Make a shallow copy

这意味着ys现在将是一个新的独立对象,其内容与xs。您可以通过检查这两个对象来验证这一点:

>>>
>>> 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.

这些孩子是not复制的。它们只是在复制的列表中再次被引用。

因此,当您修改其中的子对象之一时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。但事实证明两个都索引 1 处的子列表xs and ys被修改。同样,发生这种情况是因为我们只创建了一个浅的原始列表的副本。

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

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

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

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

制作深层副本

让我们重复前面的列表复制示例,但有一个重要的区别。这次我们要创建一个深的使用复制deepcopy()函数定义在copy模块代替:

>>>
>>> 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]]

您现在可能想花一些时间坐下来使用 Python 解释器并玩一下这些示例。当您亲自体验和使用示例时,您会更容易专注于复制对象。

顺便说一句,您还可以使用以下函数创建浅拷贝copy模块。这copy.copy()函数创建对象的浅拷贝。

如果您需要清楚地表明您正在代码中的某个位置创建浅表副本,那么这非常有用。使用copy.copy()让您表明这一事实。然而,对于内置集合,简单地使用列表、字典和集合工厂函数来创建浅拷贝被认为更Pythonic。

复制任意Python对象

我们仍然需要回答的问题是如何创建任意对象(包括自定义类)的副本(浅层和深层)。现在让我们看一下。

再次copy模块来拯救我们。它是copy.copy()copy.deepcopy()函数可用于复制任何对象。

再次强调,了解如何使用它们的最佳方法是进行简单的实验。我将基于之前的列表复制示例。让我们从定义一个简单的 2D 点类开始:

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 解释器中检查从此类创建的对象。

笔记:上面的例子使用了一个Python 3.6 f 字符串构造返回的字符串__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

还有其他需要记住的事情。因为我们的点对象使用不可变类型(int)作为其坐标,所以在这种情况下浅拷贝和深拷贝之间没有区别。但我稍后会扩展这个例子。

让我们继续看一个更复杂的例子。我将定义另一个类来表示 2D 矩形。我将以一种允许我们创建更复杂的对象层次结构的方式来完成它 - 我的矩形将使用Point对象来表示它们的坐标:

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).

我们已经在这里介绍了很多内容,对于复制对象仍然有一些更好的要点。

深入研究这个主题是值得的(哈!),所以你可能想研究一下复制模块文档。例如,对象可以通过定义来控制它们的复制方式特殊方法 __copy__()__deepcopy__()在他们。

3 件事要记住

  • 制作对象的浅表副本不会克隆子对象。因此,副本并不完全独立于原件。
  • 对象的深层复制将递归地克隆子对象。克隆完全独立于原始副本,但创建深层副本速度较慢。
  • 您可以使用以下命令复制任意对象(包括自定义类)copy模块。

如果您想深入了解其他中级 Python 编程技术,请查看此免费奖励:

免费下载: 从 Python Tricks: The Book 中获取示例章节它通过简单的示例向您展示了 Python 的最佳实践,您可以立即应用这些示例来编写更漂亮的 Python 代码。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python 对象的浅复制与深复制 的相关文章

随机推荐

  • 使用 Pandas loc 按多个条件选择数据

    熊猫优惠洛克财产 一种基于标签的数据选择方法 允许您根据多个条件选择数据 本教程旨在指导您在多种条件下使用 Pandas loc 函数 目录 hide 1 了解多个条件的逻辑运算符 2 使用 AND 运算符 组合条件 3 使用 OR 运算符
  • 使用 Python NLTK 的自然语言处理 (NLP)(简单示例)

    自然语言工具包 NLTK 是一个为符号和自然语言处理任务创建的 Python 库 它有潜力让每个人都可以进行自然语言处理 从英语到任何自然人类语言 目录 hide 1 安装 Python NLTK 2 文本预处理 3 句子和单词标记化
  • 如何在Python中对数字进行四舍五入

    处理数字是编程的重要组成部分 尤其是在科学应用或处理货币价值时 在这里 Python 中对数字进行四舍五入的需求变得至关重要 它允许我们将数字调整到附近的值 通常是为了降低其复杂性或使其达到一定的精度 让我们深入探讨如何在 Python 中
  • 使用 Sed p 命令打印行:综合指南

    The p命令输入sed用于打印当前模式空间 即当前文本行 您可以使用sed命令与p从文件中打印文本行的选项如下 sed n p filename 在本教程中 您将了解使用sed p用于打印文件或数据流中的行的命令 目录 hide 1 打印
  • Python Pandas 教程(数据分析初学者指南)

    Pandas 是一个开源的高性能库 可用于数据分析 机器学习和特征工程等多种用途 Pandas 库是数据科学家武器库中的关键工具之一 它是一个用于数据分析和操作的强大 Python 包 目录 hide 1 Python 熊猫安装 2 Pan
  • Python 中的模运算符 (%)(真实示例)

    Python 模运算符 由百分号 表示 是用于各种算术运算的数学运算符 当您使用模运算符时 它返回两个数字之间除法运算的余数 模运算符的强大功能超出了简单算术的范围 它在 Python 编程中具有广泛的实际用途 我们将在本教程中深入探讨 我
  • Python 新闻:2021 年 6 月以来的新增内容

    目录 埃瓦 乔德洛斯卡 Ewa Jodlowska 辞去 PSF 执行董事职务 PSF 公布董事会选举结果 爵士乐队接受 PSF 财政赞助计划 微软正在招聘人员来帮助加快 Python 速度 PyCon US 2021 视频录制现已推出 P
  • 使用 pdb 进行 Python 调试:总结

    以下是您在本课程中学到的所有命令 p 打印表达式的值 pp 漂亮地打印表达式的值 n 继续执行 直到到达当前函数的下一行或返回 s 执行当前行并在第一个可能的机会处停止 在被调用的函数中或在当前函数中 c 继续执行 只有遇到断点时才停止 u
  • 2021 年 3 月 24 日

    主持人 David Amos 分享了有关 Python 基础知识书籍的一些重大新闻并回答了会员的问题 在这次会议上 我们讨论了 Python 新闻和更新 Python 基础平装本 浅拷贝与深拷贝 和更多 下载 Office Hours Fi
  • 2020 年 10 月 21 日

    本周 主持人大卫 阿莫斯 David Amos 与作家一起加入盖尔 阿恩 耶勒和克里斯托弗 特鲁多庆祝Python 3 9的发布并谈论他们的Python 3 9文章和视频课程 下载 Office Hours Files 2020 10 21
  • 使用 Rich 创建 Python Wordle 克隆(概述)

    在本视频课程中 您将构建自己的Wordle克隆对于终端 自从乔什 沃德尔 Josh Wardle 推出以来沃德尔2021 年 10 月 已有数百万人玩过该游戏 虽然您可以在网络上玩原始游戏 但您将创建您的版本作为命令行应用程序 然后使用丰富
  • 多元多项式回归:代码

    以下是在 Python 中实现多重多项式回归所需的数据 x 0 1 5 1 15 2 25 5 35 11 45 15 55 34 60 35 y 4 5 20 14 32 22 38 43 x np array x y np array
  • 使用 pip 安装软件包(摘要)

    在本视频课程中 您学习了如何使用安装第三方软件包 蟒蛇的包管理器 pip 了解了终端和虚拟环境后 您看到了一些有用的pip命令 包括pip install pip list 和pip uninstall 您还学习了如何声明项目的要求并找到第
  • Python 新闻:2021 年 5 月以来的新增内容

    目录 微软成为 PSF 第三位远见赞助商 Pallets Releases New Major Versions of All Core Projects Flask 获得原生 asyncio 支持 Jinja 改进了异步环境 Click
  • 会话对象

    如果您想在多个请求中保留某些参数 可以使用会话对象 本课程将指导您完成创建新会话对象的过程以及如何使用它
  • 第 25 集:Python 中的数据版本控制和真实的 Python 视频脚本

    第 25 集 Python 中的数据版本控制和真实的 Python 视频脚本 真正的 Python 播客 2020 年 9 月 4 日 1h RSS Apple Podcasts Google Podcasts Spotify More 播
  • 第 44 集:为 PyCascades 2021 创建交互式在线 Python 会议

    第 44 集 为 PyCascades 2021 创建交互式在线 Python 会议 真正的 Python 播客 2021 年 1 月 22 日1小时4分钟 RSS Apple Podcasts Google Podcasts Spotif
  • 测试您的 Python 应用程序

    测试您的 Python 应用程序 学习路径 技能 测试 Python 代码 PyTest Mocking 没有一个开发人员是完美的 我们都会犯错误 如果不加以控制 其中一些错误可能会导致故障或错误 而恢复成本可能非常昂贵 测试您的代码有助于
  • 学习 Python 编程的 11 个初学者技巧

    目录 Make It Stick 提示 1 每天编码 技巧 2 写下来 提示 3 互动 秘诀 4 休息一下 提示 5 成为 Bug 赏金猎人 Make It Collaborative 提示 6 让自己周围都是正在学习的人 秘诀 7 教学
  • Python 对象的浅复制与深复制

    目录 制作浅副本 制作深层副本 复制任意Python对象 3 件事要记住 Python 中的赋值语句不会创建副本对于对象 它们仅将名称绑定到对象 为了不可变的对象 通常没有什么区别 但对于与可变的对象或可变对象的集合 您可能正在寻找一种方法