自底向上:从可变对象、不可变对象到深浅拷贝再到数据结构
一、不可变对象和可变对象**
Python 在 heap 中分配的对象分成两类:可变对象和不可变对象。所谓可变对象是指,对象的内容是可变的,例如 list。而不可变的对象则相反,表示其内容不可变。
不可变对象 :int,string,float,tuple -- 可理解为C中,该参数为值传递
可变对象 :list,dictionary -- 可理解为C中,该参数为指针传递
不可变对象
由于Python中的变量存放的是对象引用,所以对于不可变对象而言,尽管对象本身不可变,但变量的对象引用是可变的。运用这样的机制,有时候会让人产生糊涂,似乎可变对象变化了。如下面的代码:
for i in range(10):
print(id(i))
outputs:
2221403367696
2221403367728
2221403367760
2221403367792
2221403367824
2221403367856
2221403367888
2221403367920
2221403367952
2221403367984
从上面得知,不可变的对象的特征没有变,依然是不可变对象,变的只是创建了新对象,改变了变量的对象引用。
对于可变对象
其对象的内容是可以变化的。当对象的内容发生变化时,变量的对象引用是不会变化的。如下面的例子。
m = [1,2,3]
print(id(m))
m += [4]
print(id(m))
outputs:
2221492412544
2221492412544
对于可变对象 list这种进行操作时,相当于修改对象中某个属性,所以不会改变地址 python都是将对象的引用(内存地址)赋值给变量的。
现在问题来了
def myfunc(l):
l.append(1) # 列表加1
print(l)
l = [1,2,3]
myfunc(l) # [1,2,3,1]
print(l) # [1,2,3,1]
可变对象l
送进函数中操作后,对函数外面的也起作用,不可变对象则相反。因为函数直接对可变对象的地址的值进行了修改。该操作对于类初始化传值有一样的效果。
只有搞懂可变对象和不可变对象之后,才能理解下面的深拷贝与浅拷贝。
二、深拷贝和浅拷贝
浅拷贝
- 浅拷贝会创建一个新的容器对象(compound object)
- 对于对象中的元素,浅拷贝就只会使用原始元素的引用(内存地址)
深拷贝
- 深拷贝和浅拷贝一样,都会创建一个新的容器对象(compound object)
- 和浅拷贝的不同点在于,深拷贝对于对象中的元素,深拷贝都会重新生成一个新的对象
更进一步,由于浅拷贝复制的是元素的地址引用,如果元素是不可变类型,修改就更新了地址,和原对象的地址不同了,所以原对象不会受到影响,当元素是可变类型,修改没有改变地址,这样原对象也就跟着变化。
对于深拷贝而言,改变任何一个对象都对另一个没有影响,它们是独立的。无论是不可变对象,还是可变对象,深拷贝后它们的地址已经不一样了。
三、哈希表中的可哈希和不可哈希对象
了解了上文之后,在python中使用哈希表(散列表)数据结构时,又有可哈希和不可哈希对象之分,其实这里就是可变对象和不可变对象。为什么呢?
对于不可变类型而言,不同的值意味着不同的内存,相同的值存储在相同的内存,如果将我们的不可变对象理解成哈希表中的 Key,将内存理解为经过哈希运算的哈希值 Value,这不正好满足哈希表的性质嘛。
对于可变对象而言,比如一个列表,更改列表的值,但是对象的地址本身是不变的,也就是说不同的 Key,映射到了相同的 Value,这显然是不符合哈希值的特性的,即出现了哈希运算里面的冲突。
所以在python中使用set()、dictionary()时键值都必须是不可变对象。
参考文章:Python基础:Python可变对象和不可变对象