概念
python中的对象包含三个属性,id、type和value,id代表着对象唯一的标识符,是独一无二的,cpython中代表了存放对象的内存地址;type代表着对象的类型,比如说数字1的type就是int,字符串‘abc’的type就是str,这里还可以进一步去区分type()函数与isinstance()函数的区别,简单来说type函数不考虑继承,不会认为子类的对象属于父类,而isinstance函数考虑继承;value就是代表我们赋给对象的值。
深拷贝和浅拷贝来自于python的copy模块,可以通过import copy来导入该模块。
浅拷贝的语法为copy.copy(),深拷贝的语法为copy.deepcopy()。
变量及存储方式
值语义与引用语义
在高级语言中,变量的存储方式有两个,值语义和引用语义。
- 对于值语义,大部分语言中都是一样的,把变量的值直接存放在变量的存储区里,这样一来,每个变量所需存储区的大小是不一样的,存放数字和存放字符串所需空间大小就不同,这就需要在给变量赋值时根据值的类型声明变量的类型,静态语言比如c,c++都有值语义。
- 对于引用语义,变量中存储的只是值的引用,比如我们将整数1赋值给变量a,a中存储的是其值的引用(即地址),值1有另外的存储位置,这样做的好处是我们在给变量赋值时不需要提前声明,因为变量存储的是值对象的引用,那所有变量在内存中的大小都是一样的,就是一个地址。也被称为对象语义和指针语义。
python对象的存储方式
在python中,万物皆对象,采用的就是引用语义,赋给变量的值作为对象拥有自己的存储空间,赋值给变量,变量中存储的只是值对象的内存地址,而不是变量本身。
python中的对象可以简单的分为可变对象和不可变对象,基本数据类型比如int,float,bool是不可变的(python2中还有long),结构数据类型中,tuple,str是不可变对象,list,dict,set是可变对象,之所以是可变的,是因为变量中存储的是值对象的地址,值的地址里面存放的是元素值的地址,可以一直这样链式传递下去,所以我们对其进行内置操作(比如append,pop,remove等)都不会改变变量中存储的地址,也就表现为对象是可变的,比如说,a=[1, 2, 3],a存储的是对象[1, 2, 3]的地址,[1, 2, 3]中存储的是元素1,2,3的地址,因此要注意的是仅限于python的内置操作,别的操作比如赋值操作会改变变量的引用,即使与原来的数据一样。
a = [1, 2, 3]
print(id(a))
a = [1, 2, 3]
print(id(a))
a.append(4)
print(id(a), a)
a.pop(0)
print(id(a), a)
b = [7, 8, 9]
print(id(b))
输出结果:
6054472
6054536
6054536 [1, 2, 3, 4]
6054536 [2, 3, 4]
6054472
上述结果体现了python内置操作不会改变对象的引用,赋值操作会改变对象的引用,但是如果是c = a,那就没问题了,因为a本来就是存储着值对象的地址,赋值操作后,传递给c的实际上也是值对象的地址,所以id并不会变,有意思的是最后的b的id竟然与第一个a的id一样,这说明了当变量重新指向新的内存地址后,之前指向的内存就会被回收,建立新的变量时,又从程序自己的内存空间空间开始位置查找可分配的内存。
下面再看一个有意思的操作:
a = [1, 2, 3]
print(id(a))
a[0] = 4
print(id(a))
a[2] = [1, 2, 3]
print(id(a))
输出结果:
5071432
5071432
5071432
对列表中的元素进行赋值操作,并没有改变变量中存储的地址,但是按照前面的说法,赋值操作会改变变量指向的地址,这不是前后矛盾吗?
实际上并不矛盾,个人的理解是这样的:
前面我们也提到了,对于list这种复杂的数据结构,变量只是值对象的引用,存储值对象的地址,值对象中存储着各个元素的地址,因此,我们对元素进行赋值操作只是修改值对象中存储的单个的元素地址,并不涉及变量对值对象引用这一层面,所以变的只是元素的地址,变量的id不会变。
a = [1, 2, 3]
print(id(a), id(a[0]), id(