一,可变对象与不可变对象的区别
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