我们知道在Python编程中,即便较为权威《Python编程指南》一书,也并没有要求Python读者去掌握系统性地理解CPython内部实现中的内存分配以及内存回收等知识。甚至泛滥于网络上的Python编程技术文章很少系统完整性地谈及Python的内存管理。但是知道CPython的内存管理原理,有助于我们编写更高效的代码,有助于我们对较慢的Python代码进行故障排除。

Python的实现版本有很多,例如Jython底层就是JVM,IronPython的底层是.Net,它们的内存管理千差万别取决于底层的运行时系统。我这里说的CPython实现,当然读者若有C/C++的基础,理解CPython的内存管理机制有莫大的帮助。

在掌握CPython如何管理Python对象并将齐存储到内存中,我假定你对堆和栈有一个基本的了解。在CPython实现中,堆和栈有各自的职责

堆:主要负责存储CPython运行时的所有对象实体(也就是Python对象的所有属性数据),例如smt='Hello Word'这个字符串对象PyASCIIObject,n=23这是一个整数PyLongObject,它们都是Python对象,赋值符号“=”右边的数据值,CPython会将其存储到堆内存中。

栈:在CPython的语义中,又叫数据栈或值栈,它主要负责保存对堆中Python对象的引用,例如当CPython在执行smt='Hello Word'这个简单的Python语句,CPython会将'Hello Word'这个字符串实体所处的内存地址压入栈(对于Python语义级别理解,就是对"Hello Word"的引用),而不是将'Hello Word'这个字符串值压入栈。

从上面对smt='Hello Word'这些简单的Python赋值语句,你不能单纯地认为将‘Hello World’赋值给变量smt,这是大错特错的,由上面的CPython的简易内存模型可知。,赋值符号右边的是Python对象实体(从C实现的理解,就是构成该PyObject子类对象的属性值,这些值有具体的字面量值表示),并且CPython会为该Python对象在堆中分配内存并且存储它。而变量smt仅持有该Python对象实体的引用(从C实现的理解,就是该PyObject对象的内存地址),而不是实际的Python对象.

如果你对C/C++的指针有所理解的话,应该头脑清晰的,其实上面说那么多就是要导出一个概念,什么是Python对象的引用。再来看一个示例。

我们s1变量持有Python对象‘Hello’的引用,对于CPython虚拟机来说,就是在执行s1='Hello Word',将它的内存地址0x71334推入数据栈,那么当CPython碰到同样的语句s2='Hello Word',明显是指向同一个Python对象,那么变量s2和s1一样,它自然持有是‘Hello Word’的引用,即s2实质上拥有的‘Hello Word’的堆中的地址。

对于其他简易的数据类型,也是如出一辙的。那么现在给Python引用我们可以下一个定义。

Python对象的引用:就是Python变量持有Python对象在堆内存中的内存地址。我们可以通过python的内置id函数来正式我们刚才的分析

或者可以使用关键字is 来判断两个变量是否对同一个对象的引用。

在Python中有两种类型的对象,就是可变对象和不可变对象。

可变对象:这个很好例子,比较典型的就是list,一个列表作为一个对象存储在堆内存中,如果我们要更改该列表的某些元素,它将仍然是内存中的同一个列表对象。我们来看看下面一段非常无聊的Python代码

我们在修改列表L中,我们通过列表表达式打印出列表每个对象元素的内存地址,以及列表对象L本身的内存地址,然后在修改列表元素后,再次打印列表对象中的各个对象元素的内存地址,以及L本身的内存地址。

这段代码告诉我们CPython在内存中有如下事实list类型的L本身是一个Python对象,其对象实体就是在堆内存中。

list类型的对象,作为一个容器级别的对象,其列表存储的是元素实体的引用,而非元素实体本身。

对list对象中的某个元素的修改的本质是令被修改元素指向其他元素的引用,而我们修改该元素时,实际上CPython在堆内存中创建了一个新的对象(本例中的整数734)分配新的内存空间,并且保存该新增的对象(整数734)。L的第三个元素不再对32的引用,更新为对734的引用

list类型对象的在其元素修改前后,变量L始终引用同一个lsit对象。

那么从上面的例子,我们可以用一个内存图来表示list对象前后的变化,并且我们得知可变对象的实质:其内部元素可修改是可变更对其他Python对象的引用。其可变对象的元素可以是数字、字符串,甚至可以是其他容器级别的可变对象。

不可变对象就非常容易理解了,上面示例中list的元素对象都是不可变对象。推而广之,Python中的原始数据类型,例如数字类型(int,float)、字符串(str)、字节数组(bytes)。

题外话:整数类型不是右值吗?为什么能返回内存地址?

在Python中,一切事物都是对象,不论是整数,字符串,甚至是其他容器级别的数据类型,都由CPython的C底层由一个叫struct PyObject结构体所封装。PyObject的结构体在CPython运行时存储在堆内中,对于C底层来说,任意的PyObject结构体能够返回内存地址因此是一个左值,但对于Python语义来说,不存在静态语言中的左值和右值,它只能理解的是PyObject这个C实现的对象。

小结

我们理解了CPython的基本的内存模型后,但要说的是,这是一个简化的内存模型,CPython虚拟机对于堆内存管理有一套较为复杂的内存池管理方案。我们后面章节会逐一谈到。目前,我们至少知道了两个基本的概念什么CPython的栈和堆

什么是Python对象的引用

我们从堆内存的角度理解为什么CPython要堆Python对象分类可变对象和不可变对象,初衷是尽可能低简化堆内存的分配,因为Python变量持有Python对象的引用(或者从C底层去理解,持有PyObject对象的指针)去访问Python对象实体本身,比持有一个Python对象实体的副本更高效,更节省堆和栈的内存开销。

那么当多个Python变量引用同一个Python对象就涉及到概念就引用计数器,引用计数器属于内存垃圾回收的范畴,由引用计数又会牵涉到CPython一个致命的诟病,GIL:全局解释器锁,为什么多年来CPython不能去掉GIL,很大原因跟引用计数器有关。我们后面文章会谈到这些。

python什么是堆什么是栈_顶置篇:CPython的内存概念:栈、堆和引用相关推荐

  1. 微信小程序页面栈_微信小程序开发中的页面栈及页面路由原理

    摘要:小程序的开发方兴未艾,本文以图解的形式详细剖析了小程序开发中的页面栈及页面路由原理,对于该原理的深入理解有助于开发者更好地理解小程序的开发框架,更好地开发出功能强大的小程序. 微信小程序(以下简 ...

  2. python cpython关系_第3篇:CPython内部探究:PyASCIIObject的初始化

    在CPython3.3之后,字符串对象发生了根本性的变法,本篇我们来讨论一下字符串对象,在Include/unicodeobject.h,在整个源代码的官方文档可以归纳出几点.在CPython3.3+ ...

  3. python比flask更好的框架_(入门篇)Python框架之FastAPI——一个比Flask和Tornado更高性能的API 框架...

    用官方的话来说,FastAPI 是一种现代,快速(高性能)的 Web 框架,基于标准Python 类型提示使用 Python 3.6+ 构建 API FastAPI 站在巨人的肩膀上? 很大程度上来说 ...

  4. python自带的函数有哪些_内置函数 python自带的函数

    原博文 2018-05-25 16:27 − python自带的一些函数,直接拿过来能用的 print(bin(10)) #十进制转二进制 print(max(111,12))#取最大值 print( ...

  5. 【python股票量化_21股市精华帖_策略4篇】【代码分享】事件驱动策略初探:基于龙虎榜及机构席位数据的事件策略

    事件驱动策略初探:龙虎榜的机构席位溢价 策略来源: 20200120-长江证券-市场观察系列(一):龙虎榜的机构席位溢价 研报我放到文末让大家下载 事件策略: 主要是以下4个策略 数据获取来源 Tus ...

  6. g++ linux 编译开栈_使用g++编译器扩大程序可用栈空间

    如题,在写一些程序的时候我们有时会开一个比较大的数组或进行层数较多的dfs.这时候,程序常常会报错,于是就很无奈. 其实,虽然Windows给程序的默认栈空间比较小,我们还是有办法去扩大这个程序运行栈 ...

  7. node 获取表单数据 为空_寻offer之JS数据结构与算法 -- 栈

    栈 栈是一个线性结构,在计算机中是一种相当常见的数据结构. 栈与数组对比 我们知道数组是一种线性结构,并且可以在数组的任意位置插入和删除数据.但是有时候,我们为了实现某些功能,必须对这种任意性加以限制 ...

  8. 在Windows操作系统下,由操作系统分配的内存就叫做堆

    堆(Heap) 上面的工作是编译器做的,即程序员并不参与堆栈的维护.但上面已经说了,堆栈相当于在编译时期分配内存,因此一旦计算好某块内存的偏移,则这块内存就只能那么大,不能变化了(如果变化会导致其他内 ...

  9. python变量存储 堆与栈内存内存_浅析JS中的堆内存与栈内存

    最近跟着组里的大佬面试碰到这么一个问题, Q:说说var.let.const的区别 A:balabalabalabla... Q:const定义的值能改么? A:你逗我?不能吧 不知道各位看官怎么想? ...

最新文章

  1. SharePoint 2013 处理videoplayerpage.aspx下的个人图片显示有误问题
  2. CTO的眼界到底有多宽
  3. 用python做炒股软件-python程序源码_基于python的炒股软件
  4. 【杂谈】学深度学习的你有GPU了吗
  5. SPI-软件开发注意事项
  6. Android初级开发第七讲--特效和数据传递处理
  7. 传奇谢幕,回顾霍金76载传奇人生
  8. 【kafka】kafka 控制台 消费 ip 却找 域名 报错 Can‘t resolve address UnresolvedAddressException
  9. 快递100 的 《API URL 所支持的快递公司及参数说明》和《支持的国际类快递及参数说明》
  10. Ajax怎么解决乱码PHP,php Ajax乱码
  11. Python进阶之Scrapy抓取阳光政务平台
  12. JS,CSS是前端,JAVA PHP ASP是后端,数据库是后端的处理对象,非代表前后底
  13. awk 匹配_详解Linux三剑客之awk
  14. ios tableView那些事 (九) tableview的删除
  15. 串口通信协议示例与分析
  16. 《行为经济学》北京大学 孟涓涓 第四章
  17. iOS 如何适应 iPhone 5s/6/6 Plus 三种屏幕的尺寸?
  18. Win10 home vs pro vs enterprise vs enterprise LTSC
  19. 试用 wps 2012 抢先版(发个牢骚)
  20. Codeforces Round #324 (Div. 2) E. Anton and Ira(贪心)

热门文章

  1. Atitit 设计模式的本质思考】
  2. 【经验】在CSS中定义超链接样式a:link、a:visited、a:hover、a:active的顺序
  3. 在Linux中使用matplotlib进行科学画图
  4. 02_03 JSP内置对象之page
  5. C#获取周一、周日的日期 函数类
  6. java8(2)--- Stream API
  7. 热烈庆祝蓝启旭大佬开通博客
  8. Python模块之optparse
  9. 【python】整理的 Python 库
  10. Spark中如何管理Spark Streaming消费Kafka的偏移量