Python赋值与深浅拷贝
数据模型浅谈
对象的id
在Python中,一切数据皆为对象,对象是Python对数据的一种抽象。每一个对象皆有其identity,type,value。对象一旦创建,其id便不会改变,你可以将其视作对象在内存中的地址。is运算符比较的两个对象的id是否相同,id()函数返回代表id的整数形式。
对象的type
对象的类型决定了该对象所支持的操作,以及定义了该对象可能的值。type()函数可以获取对象的类型。和对象的id一样,对象的类型也是无法改变的。
对象的value
对象的值是可以改变的。可变对象的值是可变的,不可变对象的值是无法改变的。对象的可变性(immutability)是通过其类型来决定的,如数字类型,字符串类型和元组类型都是不可变类型,而字典类型和列表类型则是可变类型。
不可变容器对象包含可变对象时,其值是可变的。但整个容器对象却是不可变的。例如元组对象嵌套列表对象时,列表对象改变时,整个元组对象仍是不可变对象。
赋值机制
-
简单数据类型赋值。
str1 = 'hello world' str2 = str1 print('str1: ' + str1 + '; id: ' + str(id(str1))) print('str2: ' + str2 + '; id: ' + str(id(str2))) print('-' * 35) str1 = 'hi world' print('str1: ' + str1 + '; id: ' + str(id(str1))) print('str2: ' + str2 + '; id: ' + str(id(str2)))
首先,创建一个字符串对象,变量str1指向'hello world'。str2 = str1的作用是使str2也指向'hello world'。str1和str2保存相同的内存地址。
之后创建新的字符串对象'hi world',并改变str1继而指向新的字符串对象。而由输出结果可知,str2的id并没有发生改变,说明str2仍然指向第一个字符串对象。 -
列表类型赋值。
lst1 = [1, 2, 3, 4, 5] lst2 = lst1 print('lst1: ' + str(lst1) + '; id: ' + str(id(lst1))) print('lst2: ' + str(lst2) + '; id: ' + str(id(lst2))) print('*' * 40) lst1.append(6) print('lst1: ' + str(lst1) + '; id: ' + str(id(lst1))) print('lst2: ' + str(lst2) + '; id: ' + str(id(lst2))) lst2.pop() print('lst1: ' + str(lst1) + '; id: ' + str(id(lst1))) print('lst2: ' + str(lst2) + '; id: ' + str(id(lst2)))
首先,创建一个列表对象lst1,并让lst2和lst1都指向同一个列表。后面的append操作和pop操作,不管是针对lst1还是lst2,其实操作的都是同一个列表。两个变量均指向同一个列表,则对任意一个变量操作,都会影响到另一个。
总结,不管是简单或复杂的数据类型,赋值操作均将多个变量指向一个数据块。若对其中一个变量重新赋值,则另一个变量仍指向之前的数据块。对于复杂数据类型来说,简单的追加等操作,并不改变数据块的地址,也不会改变变量的实际指向。因此,对一个变量的操作,实际上是对整个数据块的操作,进而影响到其他变量。
Python中的赋值语句不会复制对象,它们会在目标和对象之间创建绑定。
关于拷贝
需求:有时候我们需要的是完全复制一份新的数据,原有数据的改变不会影响到新的变量所指向的数据。这时,Pyhton引入的拷贝的概念。
对于可变项目或包含可变项目的集合,有时需要副本,以便可以更改一个副本而不更改其他副本。Python为这种需求提供了一个专门的模块copy。包括深浅拷贝,即deepcopy和copy。浅层拷贝和深层拷贝的区别仅存在于复合对象中(对象包含其他对象,例如列表或类实例)。
-
浅拷贝
浅拷贝构造一个新的复合对象,然后(尽可能)将引用插入到原始对象中。import copy source_lst = ['str1', 'str2', 'str3', ['str4', 'str5', 'str6']] copy_lst = copy.copy(source_lst) print(source_lst) print(copy_lst) print('*' * 50) source_lst.append('append str') print(source_lst) print(copy_lst) print('*' * 50) source_lst[0] = 'modified str1' print(source_lst) print(copy_lst) print('*' * 50) source_lst[3][0] = 'modified str4' print(source_lst) print(copy_lst) print('*' * 50)
由上面的代码输出结果可知,通过copy模块的浅拷贝,对source_lst进行普通数据类型的追加,修改等操作,不会引起copy_lst的改变。但对嵌套列表的修改操作却会同时引起source_lst和copy_lst的改变,原因是浅拷贝只拷贝了嵌套列表的整个引用,并没有重新开辟新的数据空间。
-
深拷贝
深层拷贝构造一个新的复合对象,然后递归地将副本插入到原始对象中找到的对象。import copy source_lst = ['str1', 'str2', 'str3', ['str4', 'str5', 'str6']] deepcopy_lst = copy.deepcopy(source_lst) print(source_lst) print(deepcopy_lst) print('*' * 50) source_lst.append('append str') print(source_lst) print(deepcopy_lst) print('*' * 50) source_lst[0] = 'modified str1' print(source_lst) print(deepcopy_lst) print('*' * 50) source_lst[3][0] = 'modified str4' print(source_lst) print(deepcopy_lst) print('*' * 50)
仅将copy.copy(source_lst)更改为copy.deepcopy(source_lst),其他都保持不变。对source_lst进行普通数据类型的追加,修改等操作,不会引起deepcopy_lst的改变。对于source_lst的嵌套列表修改,deepcopy_lst也没有发生改变,原因是深层拷贝,采用递归的方式,拷贝所有的数据类型,重新开辟数据空间。