看这个就够了

导航

自底向上:从可变对象、不可变对象到深浅拷贝再到数据结构

一、不可变对象和可变对象**

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

image-20220413102648416

从上面得知,不可变的对象的特征没有变,依然是不可变对象,变的只是创建了新对象,改变了变量的对象引用。

对于可变对象

其对象的内容是可以变化的。当对象的内容发生变化时,变量的对象引用是不会变化的。如下面的例子。

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可变对象和不可变对象

posted on 2022-04-13 10:56  看这个就够了  阅读(97)  评论(0编辑  收藏  举报