| Python中的浅拷贝和深拷贝&可变与不可变类型 |
一. 浅拷贝深拷贝
1.1. 浅拷贝1.2. 深拷贝1.3. 总结 二. Python可变与不可变类型
一. 浅拷贝深拷贝 1.1. 浅拷贝浅拷贝: 是指重新分配一块内存,创建一个新的对象,里面的元素是原对象中子对象的引用。实现方式1: Python 中也提供了相对应的函数 copy.copy(),适用于任何数据类型实现方式2: 对于可变的序列,我们还可以通过切片操作符':'完成浅拷贝,如list;对于不可变序列,如元组,使用 tuple()或者切片操作符':'不会创建一份浅拷贝,相反,它会返回一个指向相同元组的引用。
常见的浅拷贝的方法,是使用数据类型本身的构造器,比如下面两个例子:
l1 = [1, 2, 3]
l2 = list(l1) # 构造器进行浅拷贝
l2
[1, 2, 3]
l1 == l2
True
l1 is l2
False
s1 = set([1, 2, 3]) # 构造器进行浅拷贝
s2 = set(s1)
s2
{1, 2, 3}
s1 == s2
True
s1 is s2
False
# 我们首先初始化了一个列表 l1,里面的元素是一个列表和一个元组;然后对 l1 执行浅拷贝,赋予 l2。 # 因为浅拷贝里的元素是对原对象元素的引用,因此 l2 中的元素和 l1 指向同一个列表和元组对象。 l1 = [[1, 2], (30, 40)] l2 = list(l1) # 接着往下看。l1.append(100),表示对 l1 的列表新增元素 100。这个操作不会对 l2 产生任何影响, # 因为 l2 和 l1 作为整体是两个不同的对象,并不共享内存地址。操作过后 l2 不变,l1 会发生改变: l1.append(100) # 再来看,l1[0].append(3),这里表示对 l1 中的第一个列表新增元素 3。因为 l2 是 l1 的浅拷贝, # l2 中的第一个元素和 l1 中的第一个元素,共同指向同一个列表,因此 l2 中的第一个列表也会相对应的 # 新增元素 3。操作后 l1 和 l2 都会改变: l1[0].append(3) l1 [[1, 2, 3], (30, 40), 100] l2 [[1, 2, 3], (30, 40)] # 最后是l1[1] += (50, 60),因为元组是不可变的,这里表示对 l1 中的第二个元组拼接, # 然后重新创建了一个新元组作为 l1 中的第二个元素,而 l2 中没有引用新元组,因此 l2 并不受影响。 # 操作后 l2 不变,l1 发生改变: l1[1] += (50, 60) l1 [[1, 2, 3], (30, 40, 50, 60), 100] l2 [[1, 2, 3], (30, 40)]1.2. 深拷贝
深拷贝: 是指重新分配一块内存,创建一个新的对象,并且将原对象中的元素,以递归的方式,通过创建新的子对象拷贝到新对象中。因此,新对象和原对象没有任何关联。实现方式1: Python 中以 copy.deepcopy() 来实现对象的深度拷贝。比如上述例子写成下面的形式,就是深度拷贝:
import copy l1 = [[1, 2], (30, 40)] l2 = copy.deepcopy(l1) l1.append(100) l1[0].append(3) l1 [[1, 2, 3], (30, 40), 100] l2 [[1, 2], (30, 40)]1.3. 总结
二. Python可变与不可变类型比较操作符'=='表示比较对象间的值是否相等,而'is'表示比较对象的标识是否相等,即它们 是否指向同一个内存地址。比较操作符'is'效率优于'==',因为'is'操作符无法被重载,执行'is'操作只是简单的获取对象的 ID,并进行比较;而'=='操作符则会 递归地遍历对象的所有值,并逐一比较。浅拷贝中的元素,是原对象中子对象的引用,因此,如果原对象中的元素是可变的,改变其也会影响拷贝后的对象,存在一定的副作用。深度拷贝则会递归地拷贝原对象中的每一个子对象,因此拷贝后的对象和原对象互不相关。另外,深度拷贝中会维护一个字典,记录已经拷贝的对象及其 ID,来提高效率并防止无限递归的发生。
以下所有的内容 都是基于内存地址 来说的。不可变数据类型: 当该数据类型的对应变量的值发生了改变,那么它对应的内存地址也会发生改变,对于这种数据类型,就称不可变数据类型。可变数据类型:当该数据类型的对应变量的值发生了改变,那么它对应的内存地址不发生改变,对于这种数据类型,就称可变数据类型。
在python中数据类型有:{整型,字符串,元组}不可变,{集合,列表,字典}可变。
# 1. 我们可以发现,当数据发生改变后,变量的内存地址发生了改变,那么整型就是不可变数据类型。 a = 1 print(id(a),type(a)) a = 2 print(id(a),type(a)) 19124992321912499264 # 2. 我们可以发现,当数据发生改变后,变量的内存地址发生了改变,那么字符串就是不可变数据类型。 b = 'djx' print(id(b),type(b)) b = 'djx1996' print(id(b),type(b)) 535056476344 535056476624 # 3. 元组被称为只读列表,即数据可以被查询,但不能被修改,但是我们可以在元组的元素中存放一个列表, # 通过更改列表的值来查看元组是属于可变还是不可变。 # 我们可以发现,虽然元组数据发生改变,但是内存地址没有发生了改变,但是我们不可以以此来判定元组就是可变数 # 据类型。我们回头仔细想想元组的定义就是不可变的。我们修改了元组中列表的值,但是因为列表是可变数据类型, # 所以虽然在列表中更改了值,但是列表的地址没有改变,列表在元组中的地址的值没有改变,所以也就意味着元组没 # 有发生变化。我们就可以认为元组是不可变数据类型,因为元组是不可变的。 c1 = ['1','2'] c = (1,2,c1) print(c,id(c),type(c)) c1[1] = 'djx' print(c,id(c),type(c)) result: (1, 2, ['1', '2']) 386030735432 (1, 2, ['1', 'djx']) 386030735432 # 4. 集合我们常用来进行去重和关系运算,集合是无序的。 # 我们可以发现,虽然集合数据发生改变,但是内存地址没有发生了改变,那么集合就是可变数据类型。 s = {1,'d','34','1',1} print(s,type(s),id(s)) s.add('djx') print(s,type(s),id(s)) result: {'d', 1, '34', '1'} 870405285032 {1, '34', 'djx', '1', 'd'} 870405285032 # 5. 列表是python中的基础数据类型之一,其他语言中也有类似于列表的数据类型,比如js中叫数组, # 它是以[]括起来,每个元素以逗号隔开,而且它里面可以存放各种数据类型。 # 我们可以发现,虽然列表数据发生改变,但是内存地址没有发生了改变,那么列表就是可变数据类型。 list = [1,'q','qwer',True] print(list,type(list),id(list)) list.append('djx') print(list,type(list),id(list)) result: [1, 'q', 'qwer', True] 808140621128 [1, 'q', 'qwer', True, 'djx'] 808140621128 # 6. 字典是python中唯一的映射类型,采用键值对(key-value)的形式存储数据。python对key进行哈希函数 # 运算,根据计算的结果决定value的存储地址,所以字典是无序存储的。但是在3.6版本后,字典开始是有序 # 的,这是新的版本特征。 # 字典的key值可以是整型,字符串,元组,但是不可以是列表,集合,字典。 # 我们可以发现,虽然字典数据发生改变,但是内存地址没有发生了改变,那么字典就是可变数据类型。 tuple = (1) dic = {1:2} d = { tuple:1,'key2':'djx','key3':'li'} print(d,type(d),id(d)) d['key4'] = 'haha' print(d,type(d),id(d)) result: {1: 1, 'key2': 'djx', 'key3': 'li'} 256310956320 {1: 1, 'key2': 'djx', 'key3': 'li', 'key4': 'haha'} 256310956320



