Python-可变对象-动态类型-引用传递-深拷贝

终于写完了这篇文章~

之前总觉得这里牵扯范围太多,一堆概念搅在一起,不好梳理,但这次借助之前留下的“可变对象、不可变对象”成功地把这一串都清了出来~

这次主要还是想讲引用的问题,但是各种前置概念比较多,大家就顺着看下去吧~

本篇文章涉及内容:

可变对象、不可变对象

python赋值逻辑: 动态类型

Python引用传递

深拷贝,浅拷贝

可变对象、不可变对象

这个概念可能平时不会去注意,但是不搞清楚的话,真的会对很多细节上的代码逻辑一脸懵逼

可变对象(mutable object): 对象创建后可以被改变,并且不需要重新开辟内存

不可变对象(immutable object): 对象被创建后不可改变,如要非要“改变”则只能重新开辟内存,创建新的对象

这里给个文档,是 MutableSequence, MutableSet, MutableMapping:

https://docs.python.org/2/library/collections.html#collections.MutableSequence

这个文档是collections.abc里的,abc是 Abstract Base Classes,是很多内置容器的抽象基类,同时也可以提供mix-in使用方式

常见的不可变对象: int, float, long, str, unicode, tuple, function

常见的不可变对象: list, dict, set,代码上看就是,一般会实现 , , 等方法

举例来说,str是不可变对象,改变其内容后,其id发生了变化,而list是可变对象,改变其内容后,其id不变

python的赋值逻辑: 动态类型

首先我们知道python里一切皆对象,在做赋值操作时,就是让变量指向对象的内存地址。这里给出三个概念: 变量,对象,引用

对象,就是会占据内存的东西,是有类型的,可以用内置函数type来看

变量,可以理解为在命名空间里注册的一个值,它是没有类型的,纯粹是一个方便我们使用的东西

引用,就是“变量指向对象”这个操作,一个变量只能指向一个对象,但一个对象可以被多个变量指。每个对象会有一个“引用计数器”,用来记录有多少个变量指向了它,当这个数变为0时,此对象会被gc回收,其所占据的内存空间会被释放

动态类型(dynamic typing)是什么意思呢,就是说python里的对象和变量是完全分开的,对象归内存,变量归命名空间,他们之间只会通过引用连接。

我们知道Java里声明变量时必须要指定其类型,python的“动态类型”这个名字的意思就是,变量是不需要“声明类型”的,变量也没有类型。比如一个变量一开始指向str,一会又指向list,一会又指向int,变量所指的对象代表的类型是不固定的,是动态的,这就叫做“动态类型”

对于不可变对象来说, 这个操作分三部分:

创建 这个对象

在各个命名空间里按顺序找 这个变量名,如果没有就在locals里创建

让 指向 ,此时 这个变量的被引用数从0变成1

这里是让a指向 的内存地址,而 等价于 ,也就是新创建了一个对象,内容是 ,然后让a指向这个新的地址(这里a就是一个引用)。这时,对之前的 这个对象来说,没有任何变量指向它,好可怜的,他的引用计数器归零了,立刻就会被python的gc给回收掉了

对于可变对象来说, 这个操作分四部分(顺序是我猜的,没有验证):

创建一个空list,并分配空间

创建 这三个对象,并将其引用加入list,注意加的是引用(这里忽略小数据常量池的影响)

在各个命名空间里按顺序查找 这个变量名,如果没有就在locals里创建

让b指向这个list

它与不可变对象的区别在于,当向list里添加元素的时候,list本身的地址是不变的,只是在list对应对象的内存空间里,多增加了一个新增元素的引用

动态类型,是往下继续理解的基础

python参数传递

先说结论: python的参数传递,全是引用传递,没有所谓的“值传递”

为什么呢?很好理解啊,因为变量存的都是引用,根本没有值,根本谈不上值传递。

那么为什么很多时候看起来很像值传递呢?因为看上去像值传递的都是不可变对象,因为不可以修改,所以表现形式类似于值传递,看代码:

这其实是这种情况: 多个变量指向同一个对象,即一个对象有多个引用

的时候,因为字符串是不可变对象,所以d会指向一个新的对象, 的引用计数器从2变回1,但c和d指向的完全不是同一个对象了

时,是在f对应的对象里,增加字符串 的对象的引用。而f和e指向的都是同一个对象,自然输出e的时候,也会带有对f的修改

调用函数时的参数传递,跟上面是一致的:

我们可以看到,h里的值已经被改变了~

这就是py2实现py3里 字段的重要方式,这个方式虽然不太好看,但真的是py2让外界获取闭包里的信息的常规操作,可以不用,但一定要明白

同时这样也是昨天的

Python内置模块-itertools(开一个新坑)

文章最后提出的几个问题中,第三个问题的答案: 因为对可变对象而言,对其内部数据的修改在所有引用上都可以看到效果,无论命名空间如何

深拷贝,浅拷贝

这里说的深拷贝和浅拷贝,指的是内置 模块的 和 两个函数

给一个别人的图文并茂版,这里还有不同数据类型在内存中的存储方式:

http://www.cnblogs.com/adc8868/p/5647501.html

浅拷贝的作用是,复制传入的变量指向的这个对象。这里需要注意的是,如果这是个可变对象,那么复制时会同时复制这个对象里直接存储的所有引用信息(没错是直接复制了引用,而非实际内容),因为复制的是直接存储的引用,因此这个列表里直接存储的各个对象不会被复制,只是都会增加一次引用次数。如代码,可以看到最外层id变了,但内部都仍然是一样的。

深拷贝的作用是,不但复制当前对象,还会递归进去把里面所有的对象都尝试复制一遍,这也是我们常规意义上理解的“复制”

如下面代码, 可以获取x的被引用次数,但调用这个方法的同时也会引用一次,所以最后的结果里-1就是我们想要的数据。可以看到,浅拷贝后,内部数据的id没变,但列表直接数据的第一层的引用数增加了1,而且更内层引用数没变。深拷贝后则是id全变,引用数也变成了正常的1。符合刚才的理解。

引用传递这件事,到这里也差不多就结束了,后面再有就要看gc相关的内容了,这次内容比较多,有什么地方不清楚的留言或者私信我就好~

来源:方禾黎,给我十万加急赶图的妹妹

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180804G0BD6K00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券