Python 中的赋值语句不会创建对象的拷贝,仅仅只是将名称绑定至一个对象。对于不可变对象,通常没什么差别,但是处理可变对象或可变对象的集合时,你可能需要创建这些对象的 “真实拷贝”,也就是在修改创建的拷贝时不改变原始的对象。

本文将以图文方式介绍 Python 中复制或“克隆”对象的操作。

首先介绍一下 Python 中浅拷贝与深拷贝的区别:

浅拷贝:浅拷贝意味着构造一个新的集合对象,然后用原始对象中找到的子对象的引用来填充它。从本质上讲,浅层的复制只有一层的深度。复制过程不会递归,因此不会创建子对象本身的副本。

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

赋值与引用

在开始浅拷贝与深拷贝前,我们先来看一下 Python 中的赋值与引用。

lst = [1, 2, 3]

new_list = lst

从字面上看,上述语句创建了变量 lst 和 new_list,并且 lst 和 new_list 的赋值都为一个列表。但是,Python 的赋值语句并不会复制对象,而是会重新创建一个对象的引用。

可以看出,lst 和 new_list 都引用了同一个列表。

创建浅拷贝

不少教程里都会提到,如果你有一个列表,当你想要修改列表中的值但却不想影响原始对象时,可以使用 list 复制(浅拷贝)一个列表。

我们先来试一下:

lst = [1, 2, 3]

new_list = list(lst)

没错,lst 和 new_list 分别指向了不同的列表。当修改 lst 列表中的值时,并不会对 new_list 对象产生影响。

lst[0] = 'x'

print(lst)

print(new_list)

['x', 2, 3]

[1, 2, 3]

之所以说 list 语句是浅拷贝,是因为这种修改只对一层对象有效,当列表中有子对象时,对子对象的修改将影响原始对象和浅拷贝对象。

为了解释这一说法,让我们先创建一个嵌套列表,并使用 list 函数创建浅拷贝。

lst = [[1, 2, 3], [4, 5, 6]]

new_list = list(lst)

这里 new_list 是有着和 lst 一样内容的新的独立的对象。

可以看到 lst 和 new_list 分别指向了不同的对象。

对第一层 lst 的修改,将不会对 new_list 副本造成影响。

lst.append([7, 8, 9])

print(lst)

print(new_list)

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

[[1, 2, 3], [4, 5, 6]]

但是,因为我们只创建了原始列表的一个浅拷贝,所以 new_list 仍然包含对 lst 中存储的原始子对象的引用。

也就是如上图所示,lst 和 new_list 的子列表都指向了相同的对象。

子对象没有被复制,它们只是在复制的列表中被再次引用。

因此,当你修改 lst 中的一个子对象时,这种修改也会反映到 new_list 中—— 这是因为两个列表共享相同的子对象。这种复制只是一个浅的,一个层级的复制:

lst[0][0] = 'x'

print(lst)

print(new_list)

[['x', 2, 3], [4, 5, 6], [7, 8, 9]]

[['x', 2, 3], [4, 5, 6]]

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

使用 Python 标准库中的 copy 模块可以创建深拷贝,这个模块为创建任意 Python 对象的浅拷贝和深拷贝提供了一个简单的接口。

创建深拷贝

这次我们使用 deepcopy() 函数创建一个对象的深拷贝:

import copy

lst = [[1, 2, 3], [4, 5, 6]]

new_list = copy.deepcopy(lst)

从图中可以看出 lst 和 new_list 中的子对象指向了不同的对象,如果对 lst 的子对象进行修改,将不会影响 new_list。

这一次,原始对象和复制对象都是完全独立的。如前面所说,递归克隆了 lst,包括它的所有子对象:

lst[0][0] = 'x'

print(lst)

print(new_list)

[['x', 2, 3], [4, 5, 6]]

[[1, 2, 3], [4, 5, 6]]

copy 模块中的 copy.copy() 函数也可以创建对象的浅拷贝。使用 copy.copy() 可以明确地表示创建浅拷贝。对于内置集合,简单地使用 list、dict 和 set 等工厂函数来创建浅拷贝是更加 Pythonic 的。

复制任意 Python 对象

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

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

a = Point(23, 42)

b = copy.copy(a)

print(a is b)

False

a 和 b 分别指向了不同的 Point 实例。因为我们的 Point 对象使用不可变类型(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}, {self.bottomright!r})')

# 创建一个 Rectangle 实例的浅拷贝

rect = Rectangle(Point(0, 1), Point(5, 6))

shallow_rect = copy.copy(rect)

print(rect)

print(shallow_rect)

print(rect is shallow_rect)

Rectangle(Point(0, 1), Point(5, 6))

Rectangle(Point(0, 1), Point(5, 6))

False

跟前面 list 的例子一样,rect 和 shallow_rect 的子对象都有相同的引用。在对象层级中修改一个对象,将看到这个变化也反映在浅拷贝的副本中:

rect.topleft.x = 999

print(rect)

print(shallow_rect)

Rectangle(Point(999, 1), Point(5, 6))

Rectangle(Point(999, 1), Point(5, 6))

接下来创建 Rectangle 的深拷贝并对其进行修改:

deep_rect = copy.deepcopy(rect)

deep_rect.topleft.x = 222

print(rect)

print(shallow_rect)

print(deep_rect)

Rectangle(Point(999, 1), Point(5, 6))

Rectangle(Point(999, 1), Point(5, 6))

Rectangle(Point(222, 1), Point(5, 6))

可以看出,深拷贝完全独立于原始对象和浅拷贝对象。

参阅 copy 模块文档 可以对复制进行进一步的研究。例如,对象可以通过定义特殊的方法 __copy__() 和 __deepcopy__() 来控制如何复制它们。

谨记三件事

创建对象的浅拷贝不会克隆子对象。因此,拷贝不会完全独立于原始对象。

一个对象的深拷贝会递归地克隆子对象。克隆对象完全独立于原始对象,但是创建深拷贝速度较慢。

可以使用 copy 模块复制任意对象(包括自定义类)。

python函数方法里面用浅复制深复制_图解 Python 浅拷贝与深拷贝相关推荐

  1. 用python函数写斐波那契数列通项公式_用python函数写斐波那契数列

    斐波那契数列,又称黄金分割数列.因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为"兔子数列",指的是这样一个数列:1.1.2.3.5.8.13.21.34.--在数学上, ...

  2. python初始化方法对应的变量是全局变量嘛_在Python中初始化全局变量的正确方法...

    我有一个初始化一些全局变量的 Python模块;这样的事情: #!/usr/bin/env python import re """My awesome python l ...

  3. python函数定义及调用-浅谈Python中函数的定义及其调用方法

    一.函数的定义及其应用 所谓函数,就是把具有独立功能的代码块组织成为一个小模块,在需要的时候调用函数的使用包含两个步骤 1.定义函数�C封装独立的功能 2.调用函数�C享受封装的成果 函数的作用:在开 ...

  4. as3 浅复制 深复制

    基元数据类型:boolean.int.uint.number.string 两种复杂数据类型:array.object 当数组元素全部是基元数据类型时,即全部是值类型时,是没有浅复制和深复制的区别. ...

  5. python 实例方法调用时可以忽略第一个参数_盘点Python中易忽略的函数

    Python 内置函数 1.ord() 函数是 chr() 函数(对于8位的ASCII字符串)或 unichr() 函数(对于Unicode对象)的配对函数,它以一个字符(长度为1的字符串)作为参数, ...

  6. python函数定义时带号的可变参数_在定义函数时,某个参数名字前面带有一个 * 符号表示可变长度参数,可以接收任意多个普通实参并存放于一个元组之中。_学小易找答案...

    [填空题]任意长度的 Python 列表.元组和字符串中最后一个元素的下标为 ( ) . [简答题]从键盘输入一个字符串,将小写字母全部转换成大写字母, 输出到磁盘文件"D:\test.tx ...

  7. python中的装饰器、装饰器模式_浅析Python装饰器以及装饰器模式

    漫谈 如果作为一个Python入门,不了解Python装饰器也没什么,但是如果作为一个中级Python开发人员,如果再不对python装饰器熟稔于心的话,那么可能并没有量变积累到质变. 我以前也看过很 ...

  8. python网络编程内容_图解Python网络编程

    Python Python开发 Python语言 图解Python网络编程 本篇索引 (1)基本原理 本篇指的网络编程,仅仅是指如何在两台或多台计算机之间,通过网络收发数据包:而不涉及具体的应用层功能 ...

  9. python是一种动态语言、这意味着_【python编程的优点是什么?难怪选择python的人越来越多了】- 环球网校...

    [摘要]Python是一种动态解释的.强类型定义语言:编写它时不需要定义变量类型.python判断值的类型的方法是什么,那么python编程的优点是什么?难怪选择python的人越来越多了,所以你知道 ...

最新文章

  1. UVA1108 Mining Your Own Business(思维、割点)(2011 ICPC - WorldFinal)
  2. openssl https 单向认证连接成功示例
  3. 《Python 数据分析》笔记——pandas
  4. mysql for mac中文_mysql for Mac 下创建数据表中文显示为?的解决方法
  5. 货车定位服务器维护是什么意思,有关货车日常维护的主要内容
  6. [.NET领域驱动设计实战系列]专题二:结合领域驱动设计的面向服务架构来搭建网上书店...
  7. latex中文模板_都8012年了还不用LaTex编辑论文就out了!!
  8. 商业智能在公安交通管理领域的应用
  9. 大橡塑属于机器人板块吗_又一家制造企业逃离,在东莞怒砸10亿建总部,机器人产业也顶不住深圳的高成本了?...
  10. 拓端tecdat|R语言进行数值模拟:模拟泊松回归模型的数据
  11. 苹果电脑删除linux系统软件,mac删除双系统_mac删除双系统的win
  12. java 解压Kmz文件selectNodes获取指定标签内容
  13. 多尺度小波分解Matlab/Python实现与原理分析
  14. Apache PdfBox 2.0.X 版本解析PDF文档(文字和图片)
  15. 大数据联姻“互联网+”驱动绿色变革
  16. CISP笔记 Cyrus
  17. 基于proe的阀体零件的机械加工工艺及夹具设计
  18. 使用O2OA二次开发搭建企业办公平台(二)平台部署篇:端口冲突和服务器端口配置
  19. Windows 10 如何使用iso安装.Net Framework 3.5
  20. 【181202】VC++ 实现图像中的文字提取源代码

热门文章

  1. SQL-ALTER-change和modify区别
  2. java GZIP压缩和解压
  3. 一个关于Remoting的简单例子
  4. .NET 动态脚本语言Script.NET系列文章汇总 非常精彩的应用举例
  5. sprintf()、fprintf()的使用方法
  6. 你们还在用8位单片机吗?
  7. 微处理器:50岁了!
  8. Linux下的图形库curses写贪吃蛇,酷
  9. android studio导入eclipse项目各种问题,Android | 导入Eclipse项目到Android studio的问题解决全过程...
  10. Oracle常见用法总结