第五章[列表]:5.9:列表复制的浅拷贝/深拷贝

本文详细介绍了Python中列表的浅拷贝和深拷贝概念。浅拷贝会创建新列表,但列表成员仍然指向原对象,而深拷贝不仅创建新列表,还为列表成员创建副本。文中通过实例展示了可变对象和不可变对象在浅拷贝和深拷贝下的行为差异,并列举了五种列表复制的方法。
摘要由CSDN通过智能技术生成

一,可变对象与不可变对象的区别

1,什么是可变对象?

可变对象: 对它成员的值进行修改后,id不会发生变化
可变对象有哪些?
列表、集合、字典

通过下面的例子,可以看到在列表的成员被改变后,它的id并没有变化,
注意:是成员变化,
如果被重新赋值,指向另一块内存,则id会发生变化

list1 = []
print("修改前 列表:", list1)       # []
print("修改前 列表的id:", id(list1))      # 4418570496

# 成员变化
list1.append(110)
print("修改后 列表:", list1)      # [110]
print("修改后 列表的id:", id(list1))      # 4418570496

# 成员变化
list1[0] = 1234
print("修改后 列表:", list1)      # [1234]
print("修改后 列表的id:", id(list1))      # 4418570496

# 重新赋值
list1 = [123, 456]
print("修改后 列表:", list1)      # [123, 456]
print("修改后 列表的id:", id(list1))     # 4418959488

运行结果:

修改前 列表: []
修改前 列表的id: 4418570496
修改后 列表: [110]
修改后 列表的id: 4418570496
修改后 列表: [1234]
修改后 列表的id: 4418570496
修改后 列表: [123, 456]
修改后 列表的id: 4418959488

2,什么是不可变对象?
不可变对象: 对它的值进行修改后,id会发生变化
不可变对象有哪些?
数字,布尔类型,字符串,元组

从下面的例子中可以看到,当整数或字符串的值被修改后,
id 发生了变化,说明数据的修改不是使用原来分配的内存,
而是重新申请了内存,保存新的值

i = 123
print("修改前 整数id:", id(i))

i = 456
print("修改后 整数id:", id(i))

str1 = "abc"
print("修改前 字符串id:", id(str1))

str1 = "def"
print("修改后 字符串id:", id(str1))

运行结果:

修改前 整数id: 4361518624
修改后 整数id: 4339559440
修改前 字符串id: 4361060384
修改后 字符串id: 4340861472

 3,如图所示:

二,列表变量赋值

1,id()函数

功能:id() 函数返回对象的唯一标识符,标识符是一个整数。
CPython 中 id() 函数用于获取对象的内存地址
语法

id([object])

参数说明:object — 对象
返回:返回对象的内存地址。

2,把一个列表的变量直接赋值给另一个变量时,并没有发生列表复制,
这两个变量同时指向了保存列表的内存,
所以当修改其中一个的值时,另一个也会发生变化
id() 函数用于获取对象的内存地址,
通过地址我们可以看到这两个变量是指向了相同的内存地址

用is 运算符可以看到: list_new 和 list_old指向同一个对象,
所以: 把一个变量给另一个变量赋值时,是使list_new指向list_old的内存地址,
它并没有创建新的对象,没有申请新的内存地址

# 列表变量的赋值
list_old = [1, 2, 3]
list_new = list_old
list_new[1] = "two"

print("原列表:", list_old)      # [1, 'two', 3]
print("新列表:", list_new)     # [1, 'two', 3]

print("原列表id:", id(list_old))     # 4320016576
print("新列表id:", id(list_new))     # 4320016576

print("list_new is list_old:", list_new is list_old)     # True

运行结果:

原列表: [1, 'two', 3]
新列表: [1, 'two', 3]
原列表id: 4320016576
新列表id: 4320016576
list_new is list_old: True

三,浅拷贝

1,浅拷贝的特点:

它做了两件事
首先: 为目标对象创建副本
其次: 将副本对象的成员,指向“目标对象”的成员

它的特点:给被复制的对象开辟了内存,
但没有给对象的成员开辟内存

浅拷贝为什么不给对象成员开辟内存?
意义:提交代码的执行效率,增强了内存空间的复用性

2, 浅拷贝完成时,两个列表对象的同位置的成员会指向同一块内存
如果列表的成员是不可变对象,我们修改列表元素的值后,
同位置的成员会指向不同的内存,
因为不可变对象在值变化后会重新申请内存

list_old = [1, 2, 3]
list_new = list_old.copy()

print("原列表:", list_old)      # [1, 2, 3]
print("新列表:", list_new)     # [1, 2, 3]

print("原列表id:", id(list_old))     # 4541214336
print("新列表id:", id(list_new))     # 4541416256

print("原列表 第二个元素的id:", id(list_old[1]))     # 4562263808
print("新列表 第二个元素的id:", id(list_new[1]))     # 4562263808

# 修改第二个元素的值
list_old[1] = 'a'
list_new[1] = 'b'

print("修改后 原列表:", list_old)      # [1, 'a', 3]
print("修改后 新列表:", list_new)     # [1, 'b', 3]

print("修改后 原列表 第二个元素的id:", id(list_old[1]))     # 4562297352
print("修改后 新列表 第二个元素的id:", id(list_new[1]))     # 4562298408

运行结果:

原列表: [1, 2, 3]
新列表: [1, 2, 3]
原列表id: 4541214336
新列表id: 4541416256
原列表 第二个元素的id: 4562263808
新列表 第二个元素的id: 4562263808
修改后 原列表: [1, 'a', 3]
修改后 新列表: [1, 'b', 3]
修改后 原列表 第二个元素的id: 4562297352
修改后 新列表 第二个元素的id: 4562298408

3,浅拷贝完成时,两个列表对象的同位置的元素会指向同一块内存
如果列表的元素是可变对象,我们修改任一列表元素的成员的值后,
因为可变对象在成员变化后id不变,同位置的元素仍会指向相同的内存,
这时两个列表中同位置的元素会发生相同的变化

# 当列表元素是列表时,即列表的成员是可变对象
list_old = [[1, 1], [2, 2], [3, 3]]      
list_new = list_old.copy()

print("原列表:", list_old)      # [[1, 1], [2, 2], [3, 3]]
print("新列表:", list_new)     # [[1, 1], [2, 2], [3, 3]]

print("原列表id:", id(list_old))     # 4419077888
print("新列表id:", id(list_new))     # 4419080064

print("原列表 第二个元素的id:", id(list_old[1]))     # 4418875008
print("新列表 第二个元素的id:", id(list_new[1]))     # 4418875008

# 修改第二个元素的成员的值
list_new[1][0] = ['a']

print("修改后 原列表:", list_old)      # [[1, 1], [['a'], 2], [3, 3]]
print("修改后 新列表:", list_new)     # [[1, 1], [['a'], 2], [3, 3]]

print("修改后 原列表 第二个元素的id:", id(list_old[1]))     # 4418875008
print("修改后 新列表 第二个元素的id:", id(list_new[1]))     # 4418875008

运行结果:

原列表: [[1, 1], [2, 2], [3, 3]]
新列表: [[1, 1], [2, 2], [3, 3]]
原列表id: 4419077888
新列表id: 4419080064
原列表 第二个元素的id: 4418875008
新列表 第二个元素的id: 4418875008
修改后 原列表: [[1, 1], [['a'], 2], [3, 3]]
修改后 新列表: [[1, 1], [['a'], 2], [3, 3]]
修改后 原列表 第二个元素的id: 4418875008
修改后 新列表 第二个元素的id: 4418875008

4,要注意的地方:浅拷贝完成时,两个列表对象的同位置的元素会指向同一块内存
如果列表的元素是可变对象,如果不是改变它的成员,
而是直接给它赋值为另一块内存,
则两个列表对象的同位置的元素不会发生相同变化,
而是会分别指向两块不同的内存对象

# 当列表元素是列表时,即列表的成员是可变对象
list_old = [[1, 1], [2, 2], [3, 3]]
list_new = list_old.copy()

print("原列表:", list_old)      # [[1, 1], [2, 2], [3, 3]]
print("新列表:", list_new)     # [[1, 1], [2, 2], [3, 3]]

print("原列表id:", id(list_old))     # 4400580416
print("新列表id:", id(list_new))     # 4400582592

print("原列表 第二个元素的id:", id(list_old[1]))     # 4400377472
print("新列表 第二个元素的id:", id(list_new[1]))     # 4400377472

# 重新赋值,会指向另外新分配的内存地址
list_new[1] = [5, 5]

print("修改后 原列表:", list_old)      # [[1, 1], [2, 2], [3, 3]]
print("修改后 新列表:", list_new)     # [[1, 1], [5, 5], [3, 3]]

print("修改后 原列表 第二个元素的id:", id(list_old[1]))     # 4400377472
print("修改后 新列表 第二个元素的id:", id(list_new[1]))     # 4562297352

运行结果:

原列表: [[1, 1], [2, 2], [3, 3]]
新列表: [[1, 1], [2, 2], [3, 3]]
原列表id: 4400580416
新列表id: 4400582592
原列表 第二个元素的id: 4400377472
新列表 第二个元素的id: 4400377472
修改后 原列表: [[1, 1], [2, 2], [3, 3]]
修改后 新列表: [[1, 1], [5, 5], [3, 3]]
修改后 原列表 第二个元素的id: 4400377472
修改后 新列表 第二个元素的id: 4562297352

四,五种列表的复制(浅拷贝)方式

说明:这里的复制是成功的,但仍然是浅拷贝

1,用列表的copy()方法

copy()方法:
功能:用于复制列表,类似于 a[:]
语法:

list.copy()

参数:无
返回:返回复制后的新列表。

# 用copy()方法复制
list_old = [[1, 1], [2, 2], [3, 3]]
list_new = list_old.copy()
list_new[1][1] = "a"

print("原列表:", list_old)     # [[1, 1], [2, 'a'], [3, 3]]
print("copy:新列表:", list_new)     # [[1, 1], [2, 'a'], [3, 3]]

运行结果:

原列表: [[1, 1], [2, 'a'], [3, 3]]
copy:新列表: [[1, 1], [2, 'a'], [3, 3]]

2,用列表的切片复制

# 用切片复制
list_old = [[1, 1], [2, 2], [3, 3]]
list_new = list_old[:]
list_new[1][1] = "b"

print("原列表:", list_old)     # [[1, 1], [2, 'b'], [3, 3]]
print("切片:新列表:", list_new)     # [[1, 1], [2, 'b'], [3, 3]]

运行结果:

原列表: [[1, 1], [2, 'b'], [3, 3]]
切片:新列表: [[1, 1], [2, 'b'], [3, 3]]

3,用list()函数复制

list()方法
功能:用于将列表/元组/字典/整数序列等转换为列表。
语法:

list( tup )

参数:tup — 要转换为列表的列表/元组/字典等
返回: 返回转换得到的列表

# 用list()函数复制
list_old = [[1, 1], [2, 2], [3, 3]]
list_new = list(list_old)
list_new[1][1] = "c"

print("原列表:", list_old)     # [[1, 1], [2, 'c'], [3, 3]]
print("list():新列表:", list_new)     # [[1, 1], [2, 'c'], [3, 3]]

运行结果:

原列表: [[1, 1], [2, 'c'], [3, 3]]
list():新列表: [[1, 1], [2, 'c'], [3, 3]]

4,用列表推导式复制

# 用列表推导式复制
list_old =  [[1, 1], [2, 2], [3, 3]]
list_new = [x for x in list_old]
list_new[1][1] = "d"

print("原列表:", list_old)       # [[1, 1], [2, 'd'], [3, 3]]
print("列表推导式:新列表:", list_new)       # [[1, 1], [2, 'd'], [3, 3]]

运行结果:

原列表: [[1, 1], [2, 'd'], [3, 3]]
列表推导式:新列表: [[1, 1], [2, 'd'], [3, 3]]

5,用copy模块的copy()方法复制

from copy import copy

# 用copy模块的copy()方法复制
list_old =  [[1, 1], [2, 2], [3, 3]]
list_new = copy(list_old)
list_new[1][1] = "e"

print("原列表:", list_old)     # [[1, 1], [2, 'e'], [3, 3]]
print("copy模块:新列表:", list_new)      # [[1, 1], [2, 'e'], [3, 3]]

运行结果:

原列表: [[1, 1], [2, 'e'], [3, 3]]
copy模块:新列表: [[1, 1], [2, 'e'], [3, 3]]

五,深拷贝

1,浅拷贝和深拷贝的相同与不同:

浅拷贝做了两件事
首先: 为目标对象创建副本
其次: 将副本对象的成员,指向“目标对象”的成员

它的特点:给被复制的对象分配了内存,
但没有给对象的成员分配内存

深拷贝做了三件事:
为“目标对象”创建副本(分配内存)
为目标对象的成员“创建副本”,以及成员的对象(分配内存)
将“副本对象”的成员,指向新创建的副本成员

它的特点:给被复制的对象分配了内存,
也给对象的成员分配了内存

对于不可变对象(如数字、字符串、元组等),浅拷贝和深拷贝都是相同的,
对于可变对象(如列表、字典等),
浅拷贝将副本对象的成员,指向“目标对象”的成员,不会递归复制对象的成员
如果需要递归复制对象的成员,必须使用深拷贝

2,深拷贝的实现方法:

需要引入copy模块,使用它的deepcopy()函数来实现深拷贝
可以看到,原列表和新列表各自的第二个元素的id并不相同,
所以当它的成员发生变化时,因为两个列表的成员也分配了不同的内存空间,
所以互相不受影响

import copy

# 使用copy模块,深拷贝
list_old = [[1, 1], [2, 2], [3, 3]]
list_new = copy.deepcopy(list_old)

print("原列表:", list_old)      # [[1, 1], [2, 2], [3, 3]]
print("新列表:", list_new)     # [[1, 1], [2, 2], [3, 3]]

print("原列表id:", id(list_old))     # 4502481088
print("新列表id:", id(list_new))     # 4502481216

print("原列表 第二个元素的id:", id(list_old[1]))     # 4501620992
print("新列表 第二个元素的id:", id(list_new[1]))     # 4502486784

# 修改第二个元素的第1个成员的值
list_new[1][0] = "a"

print("修改后 原列表:", list_old)      # [[1, 1], [2, 2], [3, 3]]
print("修改后 新列表:", list_new)     # [[1, 1], ['a', 2], [3, 3]]

print("修改后 原列表 第二个元素的id:", id(list_old[1]))     # 4501620992
print("修改后 新列表 第二个元素的id:", id(list_new[1]))     # 4502486784

运行结果:

原列表: [[1, 1], [2, 2], [3, 3]]
新列表: [[1, 1], [2, 2], [3, 3]]
原列表id: 4502481088
新列表id: 4502481216
原列表 第二个元素的id: 4501620992
新列表 第二个元素的id: 4502486784
修改后 原列表: [[1, 1], [2, 2], [3, 3]]
修改后 新列表: [[1, 1], ['a', 2], [3, 3]]
修改后 原列表 第二个元素的id: 4501620992
修改后 新列表 第二个元素的id: 4502486784
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老刘你真牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值