python函数方法里面用浅复制深复制_图解 Python 浅拷贝与深拷贝
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 浅拷贝与深拷贝相关推荐
- 用python函数写斐波那契数列通项公式_用python函数写斐波那契数列
斐波那契数列,又称黄金分割数列.因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为"兔子数列",指的是这样一个数列:1.1.2.3.5.8.13.21.34.--在数学上, ...
- python初始化方法对应的变量是全局变量嘛_在Python中初始化全局变量的正确方法...
我有一个初始化一些全局变量的 Python模块;这样的事情: #!/usr/bin/env python import re """My awesome python l ...
- python函数定义及调用-浅谈Python中函数的定义及其调用方法
一.函数的定义及其应用 所谓函数,就是把具有独立功能的代码块组织成为一个小模块,在需要的时候调用函数的使用包含两个步骤 1.定义函数�C封装独立的功能 2.调用函数�C享受封装的成果 函数的作用:在开 ...
- as3 浅复制 深复制
基元数据类型:boolean.int.uint.number.string 两种复杂数据类型:array.object 当数组元素全部是基元数据类型时,即全部是值类型时,是没有浅复制和深复制的区别. ...
- python 实例方法调用时可以忽略第一个参数_盘点Python中易忽略的函数
Python 内置函数 1.ord() 函数是 chr() 函数(对于8位的ASCII字符串)或 unichr() 函数(对于Unicode对象)的配对函数,它以一个字符(长度为1的字符串)作为参数, ...
- python函数定义时带号的可变参数_在定义函数时,某个参数名字前面带有一个 * 符号表示可变长度参数,可以接收任意多个普通实参并存放于一个元组之中。_学小易找答案...
[填空题]任意长度的 Python 列表.元组和字符串中最后一个元素的下标为 ( ) . [简答题]从键盘输入一个字符串,将小写字母全部转换成大写字母, 输出到磁盘文件"D:\test.tx ...
- python中的装饰器、装饰器模式_浅析Python装饰器以及装饰器模式
漫谈 如果作为一个Python入门,不了解Python装饰器也没什么,但是如果作为一个中级Python开发人员,如果再不对python装饰器熟稔于心的话,那么可能并没有量变积累到质变. 我以前也看过很 ...
- python网络编程内容_图解Python网络编程
Python Python开发 Python语言 图解Python网络编程 本篇索引 (1)基本原理 本篇指的网络编程,仅仅是指如何在两台或多台计算机之间,通过网络收发数据包:而不涉及具体的应用层功能 ...
- python是一种动态语言、这意味着_【python编程的优点是什么?难怪选择python的人越来越多了】- 环球网校...
[摘要]Python是一种动态解释的.强类型定义语言:编写它时不需要定义变量类型.python判断值的类型的方法是什么,那么python编程的优点是什么?难怪选择python的人越来越多了,所以你知道 ...
最新文章
- UVA1108 Mining Your Own Business(思维、割点)(2011 ICPC - WorldFinal)
- openssl https 单向认证连接成功示例
- 《Python 数据分析》笔记——pandas
- mysql for mac中文_mysql for Mac 下创建数据表中文显示为?的解决方法
- 货车定位服务器维护是什么意思,有关货车日常维护的主要内容
- [.NET领域驱动设计实战系列]专题二:结合领域驱动设计的面向服务架构来搭建网上书店...
- latex中文模板_都8012年了还不用LaTex编辑论文就out了!!
- 商业智能在公安交通管理领域的应用
- 大橡塑属于机器人板块吗_又一家制造企业逃离,在东莞怒砸10亿建总部,机器人产业也顶不住深圳的高成本了?...
- 拓端tecdat|R语言进行数值模拟:模拟泊松回归模型的数据
- 苹果电脑删除linux系统软件,mac删除双系统_mac删除双系统的win
- java 解压Kmz文件selectNodes获取指定标签内容
- 多尺度小波分解Matlab/Python实现与原理分析
- Apache PdfBox 2.0.X 版本解析PDF文档(文字和图片)
- 大数据联姻“互联网+”驱动绿色变革
- CISP笔记 Cyrus
- 基于proe的阀体零件的机械加工工艺及夹具设计
- 使用O2OA二次开发搭建企业办公平台(二)平台部署篇:端口冲突和服务器端口配置
- Windows 10 如何使用iso安装.Net Framework 3.5
- 【181202】VC++ 实现图像中的文字提取源代码
热门文章
- SQL-ALTER-change和modify区别
- java GZIP压缩和解压
- 一个关于Remoting的简单例子
- .NET 动态脚本语言Script.NET系列文章汇总 非常精彩的应用举例
- sprintf()、fprintf()的使用方法
- 你们还在用8位单片机吗?
- 微处理器:50岁了!
- Linux下的图形库curses写贪吃蛇,酷
- android studio导入eclipse项目各种问题,Android | 导入Eclipse项目到Android studio的问题解决全过程...
- Oracle常见用法总结