对于新手来说,Python 中有哪些难以理解的概念?
老手都是从新手一路过来的,提起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 类型为例也验证了这个变量存储管理的机制。
———————— int example————————
i = 5 ——》》》 i ---> 5 id(i) ---> 0xa26f880
i += 1 ——》》》 i ---> 6 id(i) ---> 0xa26f874
j = 5 ——》》》 j ---> 5 id(j) ---> 0xa26f880
________________ float example_______________
i = 1.5 ——》》》 i ---> 1.5 id(i) ---> 0x9e86c8c
i += 1 ——》》》 i ---> 2.5 id(i) ---> 0x9e86cac
j = 1.5 ——》》》 j ---> 1.5 id(j) ---> 0x9e86c8c
陆陆续续的试了列表、字典、字符串、元组等变量类型赋值的效果,我发现其实Python中的对象分为可变类型和不可变类型,列表、字典是可变类型,而整数、浮点、短字符串、元组等是不可变类型。可变类型的变量赋值与我们了解的C语言机制相同,而不可变类型的变量赋值时,实际上是重新创建一个不可变类型的对象,并将原来的变量重新指向新创建的对象,当然如果没有其他变量引用原有对象时,原有对象就会被回收。这也是Python作为动态类型语言的特点,即变量不需要预先声明类型,当变量在赋值时解释器会根据值的类型创建对应的内存空间进行存储,并将变量指向这个地址空间即可,比如运行a=1时,解释器将变量指向整形值1的地址,当运行a=0.1时,解释器将变量指向浮点值0.1的地址。

但是问题又来了!!!为什么Python可以这样肆无忌惮地完成动态类型的特征?
这里要深究到PyObject这个结构体的层面。通常来说,无论什么语言最终被计算机识别到的都是内存中的字节信息,对象实际上就是在更高的层次上把内存上的数据作为一个整体来考虑,比如一个整数或是一个字符串。Python中所有的东西都是对象,它们拥有一些相同的内容,这些内容定义在PyObject这个结构体中。
typedef struct _object {
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
} 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变量赋值的机制以后,大家就不觉得这是难以理解的概念了吧,其实学习的乐趣就体现在恍然大悟、融会贯通的那一时刻。

对于新手来说,Python 中有哪些难以理解的概念?的更多相关文章
- RxSwift 系列(九) -- 那些难以理解的概念
前言 看完本系列前面几篇之后,估计大家也还是有点懵逼,本系列前八篇也都是参考RxSwift官方文档和一些概念做的解读.上几篇文章概念性的东西有点多,一时也是很难全部记住,大家脑子里面知道有这么个概念就 ...
- 转发对python装饰器的理解
[Python] 对 Python 装饰器的理解的一些心得分享出来给大家参考 原文 http://blog.csdn.net/sxw3718401/article/details/3951958 ...
- Python中文字符的理解:str()、repr()、print
Python中文字符的理解:str().repr().print 字数1384 阅读4 评论0 喜欢0 都说Python人不把文字编码这块从头到尾.从古至今全研究通透的话是完全玩不转的.我终于深刻的理 ...
- 新手学python(3):yield与序列化
1 Yield生成器 Yield是我在其他语言中没有见过的一个属性,算是python的一大特色,用好之后可以使代码更简洁.考虑一个简单的例子,文件的遍历.要遍历一个目录下的所有文件需要递归的操作.如果 ...
- 【python进阶】深入理解系统进程2
前言 在上一篇[python进阶]深入理解系统进程1中,我们讲述了多任务的一些概念,多进程的创建,fork等一些问题,这一节我们继续接着讲述系统进程的一些方法及注意点 multiprocessing ...
- 难以理解的AQS(下)
在上一篇博客,简单的说下了AQS的基本概念,核心源码解析,但是还有一部分内容没有涉及到,就是AQS对条件变量的支持,这篇博客将着重介绍这方面的内容. 条件变量 基本应用 我们先通过模拟一个消费者/生产 ...
- 通过作用域链解析js函数一些难以理解的的作用域问题
基本原理 js函数在执行时,系统会创建一个隐式的属性scope,scope中存储的是函数的作用域链. 通过对这个scope的分析,就能解释JavaScript中许多难以理解的问题: 例1: funct ...
- 在python 中有时候我们用数组
在python 中有时候我们用数组操作数据可以极大的提升数据的处理效率, 类似于R的向量化操作,是的数据的操作趋于简单化,在python 中是使用numpy模块可以进行数组和矢量计算. 下面来看下简单 ...
- python 导入模块 import 理解
--python 导入模块 import 理解 -----------------------------------2014/03/18 python 导入一个模块的过程要求有一个叫做“路径搜索”的 ...
随机推荐
- Android Studio更改工程名异常解决方案 :can't rename root module
在修改Android Studio 中 project的名字时 ,提示 “can’t rename root module”. 这是因为Android Studio只能修改根目录内的所有文件,要修改p ...
- Java泛型学习一
Java泛型 所谓泛型,就是变量类型的参数化.泛型是java1.5中引入的一个重要特征,通过引入泛型,可以使编译时类型安全,运行时更少抛出ClassCastException的可能.一提到参数化,最熟 ...
- Msys/MinGW与Cygwin/gcc
一. MinGW MinGW 官方网站为 http://www.mingw.org/ MinGW,即 Minimalist GNU For Windows(GCC compiler suite).它是 ...
- 你知道CAN/RS-485总线为什么要隔离吗?
您在使用CAN或RS-485总线进行调试时,是否遇到过偶尔通信出错?或者接收不到数据?一直正常使用的总线,突然出现大范围的错误,或者节点损坏?您还在为这些问题不知所措,摸不着头脑吗?使用总线隔离,或许 ...
- 【Git】Git Bash
版本控制工具Git Bash软件 安装 git.exe 即可
- Dom操作(标签--增、删、移动)
Dom操作 移动或者插入标签的方法 1.append()和appendTo():在现存元素的内部,从后面放入元素: 先声明一个变量用来保存新标签 var $span = $('这是一个span元素') ...
- ETL项目2:大数据清洗,处理:使用MapReduce进行离线数据分析并报表显示完整项目
ETL项目2:大数据清洗,处理:使用MapReduce进行离线数据分析并报表显示完整项目 思路同我之前的博客的思路 https://www.cnblogs.com/symkmk123/p/101974 ...
- C语言中关于逗号运算符的理解
在C语言中运算符有很多,包括算数运算符.关系运算符.赋值运算符.位运算符.逻辑运算符.三目运算符.sizeof运算符.逗号运算符等等,那今天我们就重点来给大家讨论一下关于逗号运算符的一些运算规则. 首 ...
- 《Java 程序设计》课堂实践项目-Arrays和String单元测试
<Java 程序设计>课堂实践项目-Arrays和String单元测试 课后学习总结 目录 改变 Arrays和String单元测试实验要求 课堂实践成果 课后思考 改变 修改了博客整体布 ...
- BZOJ2539 Spoj 10707 Count on a tree II
题面 题解 因为这道题目我也不太会做,所以借鉴了一下大佬heyujun的博客 如果不强制在线,这道题目是树上莫队练手题 我们知道莫队是离线的,但是万一强制在线就凉凉了 于是我们就需要一些操作:树分块 ...