由于Python的动态特性,Python中变量和对象是分离的,在使用变量的时候,只需要直接赋值即可(在给变量赋值前不需要声明,C/C++等静态语言需要声明),而在Python中,一切皆对象,所以变量的本质是对对象的引用。
例子 一
a = 1
实质上是a指向了对象1,可以用命令行模式id()来查看对象的身份
例子二:
a = 1
a = "apple"
a变量先引用了对象1,再引用了对象apple,可以看到,a不再指向对象1,而对象1会被Python的内存机制当作垃圾回收处理掉。
例子三:
a =2
b = 2
按照例一的说法,应该是这样,a,b分别引用了两个对象(只不过都是为2)
用函数查看变量a和b的引用情况:
发现,a和b指向了同一地址,也就是指向了同一对象。这个跟python的内存机制有关系,因为对于语言来说,频繁的进行对象的销毁和建立,特别浪费性能。所以在Python中,整数和短小的字符,Python都会缓存这些对象,以便重复使用。
所以实际上是这样:
相当于你今天穿着a牌子的衣服,明天穿着b牌子的衣服,你说你再也不是自己了,是不是有点2?
例子四:
a = 3
b = a(即让变量b指向对象a,但a也是指向另一个对象,即b指向对象a指向的对象)
a = a + 2
可以看到 a 的引用改变了,但是 b 的引用未发生改变;a,b指向不同的对象; 第3句对 a 进行了重新赋值,让它指向了新的 对象了;即使是多个引用指向同一个对象,如果一个引用值发生变化,那么实际上是让这个引用指向一个新的引用,并不影响其他的引用的指向。从效果上看,就是各个引用各自独立,互不影响。
例子五:
L1 = [1,2,3]
L2 = L1
L1和L2引用一样,指向了同一列表对象。赋值即引用。接下来,我们操作
L1[0] = 5
L1 和 L2 的引用没有发生任何变化,但是 列表对象[1,2,3] 的值 变成了 [5,2,3](列表对象改变了)
在该情况下,我们不再对L1这一引用赋值,而是对L1所指向的表的元素赋值。结果是,L2也同时发生变化。
原因何在呢?因为L1,L2的指向没有发生变化,依然指向那个表。表实际上是包含了多个引用的对象(每个引用是一个元素,比如L1[0],L1[1]..., 每个引用指向一个对象,比如1,2,3), 。而L1[0] = 5这一赋值操作,并不是改变L1的指向,而是对L1[0], 也就是表对象的一部份(一个元素),进行操作,所以所有指向该对象的引用都受到影响。
(与之形成对比的是,我们之前的赋值操作都没有对对象自身发生作用,只是改变引用指向。)
列表可以通过引用其元素,改变对象自身(in-place change)。这种对象类型,称为可变数据对象(mutable object),词典也是这样的数据类型。而像之前的数字和字符串,不能改变对象本身,只能改变引用的指向,称为不可变数据对象(immutable object)。
检查两个对象是否指向同一对象,可用关键字 is判断
例子六:引用对象的类型与拷贝
a = [1,2,3,[4,5]]
浅拷贝:c = copy.copy(a)
说明c 和a 指向了不同的对象,但是两个数值一样的(跟例三的对比)
深拷贝:d = copy.deepcopy(a)
显然d 也是指向了与a不同的对象,这两个对象也是一样的。
总结,赋值是指向同一个对象(即引用对象本身),拷贝是创建了新的对象(新对象包含的对象和原来的一样)
那么,c 和 d 是什么关系,会不会相等?(一个浅一个深,肯定有区别)
现在来解释背后的原理
1.对于浅拷贝:copy.copy 浅拷贝 只拷贝父对象,不会拷贝对象的内部的子对象。(比深拷贝更加节省内存)
2. copy.deepcopy 深拷贝 拷贝对象及其子对象
故对于浅拷贝,a在对父对象做改变的时候,c不会有变化,因为是两个对象,但对子对象,也就是上面的【4,5】改变,由于a和c引用的是同一对象,故两者都改变。
而对于深拷贝,由于两者属于独立的,d不会随着a的改变而改变。
例如:
在a对象的后面添加"test",即父对象改变
可以看到,c 和 d都没有变化
如果对子对象【4,5】做改变的话呢?
注意到子对象属于父对象的一个元素(a[3])
a[3].append("Python")
可以看到,对子对象的改变,c也改变了,d依然保持不变。
参考博客: