老手都是重新手一路过来的,提起Python中难以理解的概念,可能不少人对于Python变量赋值的机制有些疑惑,不过对于习惯于求根究底的程序员,只有深刻理解了某个事物本质,掌握了它的客观规律,才能驾轻就熟、运用自如,进阶更高层次来看待这个事物,此刻“庖丁解牛”这个成语可以贴切表达这个意思,你看见的是整头的牛,而我看见的是牛的内部肌理筋骨,就是这个状态!!!程序员
那么为何Python变量赋值的机制难以理解呢?学习
我想多是咱们的思惟被C语言变量赋值的机制所固化了。在C语言中变量所分配到的地址是内存空间中一个固定的位置,当咱们改变变量值时,对应内存空间中的值也相应改变。在Python中变量存储的机制是彻底不同的,当给一个变量赋值时首先解释器会给这个值分配内存空间,而后将变量指向这个值的地址,那么当咱们改变变量值的时候解释器又会给新的值分配另外一个内存空间,再将变量指向这个新值的地址,因此和C语言相比,在Python中改变的是变量所指向的地址,而内存空间中的值是固定不变的。测试
接下来咱们要由浅入深的去验证下咱们的结论。在Ubuntu 16.04 LTS 32 位的环境下经过id方法查看变量的内存地址的方式来进行验证,为何要强调环境呢?由于不一样的环境下测试结果可能会因为解释器的优化不一样而有所不一样。优化
那这里咱们就以Python的int类型为例,能够看到执行 i += 1 后,变量i的内存地址会发生变化,事实上 i += 1 并非在原有变量i的地址上加1,而是从新建立一个值为6的int对象,变量i则引用了这个新的对象,所以当变量i和变量j的值相同时会指向同个内存地址。一样以Python的float 类型为例也验证了这个变量存储管理的机制。指针
陆陆续续的试了列表、字典、字符串、元组等变量类型赋值的效果,我发现其实Python中的对象分为可变类型和不可变类型,列表、字典是可变类型,而整数、浮点、短字符串、元组等是不可变类型。可变类型的变量赋值与咱们了解的C语言机制相同,而不可变类型的变量赋值时,其实是从新建立一个不可变类型的对象,并将原来的变量从新指向新建立的对象,固然若是没有其余变量引用原有对象时,原有对象就会被回收。这也是Python做为动态类型语言的特色,即变量不须要预先声明类型,当变量在赋值时解释器会根据值的类型建立对应的内存空间进行存储,并将变量指向这个地址空间便可,好比运行a=1时,解释器将变量指向整形值1的地址,当运行a=0.1时,解释器将变量指向浮点值0.1的地址。对象
可是问题又来了!!!为何Python能够这样肆无忌惮地完成动态类型的特征?blog
这里要深究到PyObject这个结构体的层面。一般来讲,不管什么语言最终被计算机识别到的都是内存中的字节信息,对象实际上就是在更高的层次上把内存上的数据做为一个总体来考虑,好比一个整数或是一个字符串。Python中全部的东西都是对象,它们拥有一些相同的内容,这些内容定义在PyObject这个结构体中。内存
ob_refcnt是一个整形变量,它的做用是实现引用计数机制。好比一个对象A,当有一个新的PyObject *引用该对象时,A的引用计数增长;而当这个PyObject *被删除时,A的引用计数减小。当A的引用计数减小到0时,A就能够从堆上被删除,以释放出内存供别的对象使用。ob_type是一个指向_typeobject结构体的指针,这个结构体实际上也是一个对象,它是用来指定一个对象类型的类型对象,这个类型对象记录了不一样的对象所需的内存空间的大小信息。那么简单的说,Python中对象机制的核心一个是引用计数,一个就是类型。字符串
PyObject是一个定长对象的结构体,对于可变长度对象的结构体是PyVarObject,它比PyObject结构体多一个ob_size变量,用于指定容器中包含的元素数量。好比list中有5个元素,那么PyVarObject.ob_size的值就是5。PyVarObject实际上只是对PyObject的一个扩展而已,任何一个PyVarObject所占用的内存,开始部分的字节定义和PyObject是同样的。这就能够解释说,当Python建立一个整形对象PyIntObject,首先它会为这个对象分配内存,并进行初始化,而后这个对象会由一个PyObject*变量来维护,由于每个对象都拥有相同的对象头部,这使得对象的引用变得很是的统一。不管对象实际上的类型是什么,只须要经过PyObject*指针就能够引用任意的一个对象。容器
深刻浅出了Python变量赋值的机制之后,你们就不以为这是难以理解的概念了吧,其实学习的乐趣就体如今恍然大悟、融会贯通的那一时刻!