面试题

分别给出下述代码在终端(e.g. IPyhon)中和在程序中的运行结果

a = 256
b = 256c = 257
d = 257def foo():e = 257f = 257print('a is b: %s' % (a is b))print('c is d: %s' % (c is d))print('c is e: %s' % (c is e))print('e is f: %s' % (e is f))foo()
  • IPython 中运行的结果:
In [31]: a = 256In [32]: b = 256In [33]: c = 257In [34]: d = 257In [35]: def foo():...:         e = 257...:         f = 257...:...:         print('a is b: %s' % (a is b))...:         print('c is d: %s' % (c is d))...:         print('c is e: %s' % (c is e))...:         print('e is f: %s' % (e is f))...:
In [36]: foo()
a is b: True
c is d: False
c is e: False
e is f: True
  • 在程序中运行的结果:
$ python foo.pya is b: True
c is d: True
c is e: False
e is f: True

Emmmmm~ 显然两次执行的结果不尽相同,实际上在这个简单的代码之中包含了两个鲜为人知的 Python 技术内幕。

小整数与大整数

整数是最为简单且常用的数据类型,尤其在极端的科学计算场景中,上百万次计算就发生在数秒之间。对于这些场景,如果 Python 仍单纯的使用 malloc/free 函数来完成内存的分配与释放,那么其运行性能将会及其低下,并且会造成很大的浪费。所以,出于性能的考虑,Python 在内部对整数的实现做了许多优化工作,而优化的核心就是减少 malloc/free 函数的调用。

同时又因为在实际的应用中,应用程序对整数的使用有明显的数值区间划分。例如,数值较小的整数会更频繁的被使用,而数值较大的整数虽然使用得不那么频繁,但却要占用更大的内存空间。为了更好的区分优化,在 Python 的源码实现中,将整数的定义细分为「小整数」和「大整数」,前者的数值范围在 [-5, 257) 之间,其余的数值均归为后者。

小整数对象缓存池

小整数的使用是最为频繁的,为了避免反复创建和销毁带来的资源开销,Pyhton 干脆直接将这些小整数都缓存到一个特定的 small_ints 链表中,该链表会存在于 Python 解释器的整个生命周期中,但凡需要使用小整数时,则直接从链表中获取。这就是Python 的「小整数对象缓存池技术」,简单来说就是小整数对象会在 Python 全局解释器范围内被重复引用,且永远不会被 GC 回收。那么对于小整数而言,只会在初始化 small_ints 时调用 malloc/free 函数

通用整数对象缓冲池

Python 运行环境会为大整数对象分配一定的缓冲内存空间,该内存空间会被大整数对象轮流使用,直到占满为止,再继续再开辟一块新的内存空间。这就是 Python 的「通用整数对象缓冲池技术」。

通用整数对象缓冲池相关的结构体定义

struct _intblock {  struct _intblock *next;  PyIntObject objects[N_INTOBJECTS];
};  
typedef struct _intblock PyIntBlock;static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;  

PyIntObject(Python 整数对象)会以数组的形式存在于 PyIntBlock 中,一个 block 大约能够存放 82 个 PyIntObject。block_list 用于维护分配给 PyIntObject 所有的内存空间,而 free_list 则用于维护 PyIntObject 可用的剩余内存空间。只有当 free_list 为 NULL(剩余空间为 0)时,Python 才会调用 fill_free_list 函数再 malloc 出来一个 block。并且当一个大整数对象的引用计数为 0 而需要被回收时,其占有的内存并不会归还给系统,而是重新回到 free_list,供新创建的整数对象使用。由此可见,通用整数对象缓冲池同样能够有效的减少 malloc/free 函数的调用。

在理解了大、小整数实现的不同后,再看看下面的运行结果,我想大家应该不会再感到奇怪:

In [25]: a = 256In [26]: b = 256In [27]: a is b
Out[27]: TrueIn [28]: c = 257In [29]: d = 257In [30]: c is d
Out[30]: False

但这依旧不足以解释面试题中同为大整数的变量 c、d、e、f,为什么 c/d、e/f 的内存地址却是两两相同的结果。这就涉及到了另一个知识点——「Python 的解析模式」。

逐行解释与整体解释的差异

整体解释

整体解释指的是通过应用程序的方式来运行 Python 代码,对应面试题在程序中运行的结果。对于此时的 Python 代码而言,解析器 CPython 的「编译单元」是一个函数(Python 顶层代码也被当作一个函数来进行编译),即题目中的函数 foo 会被单独编译,而得到一个 PyFunctionObject 对象,该对象中包含了字节码、常量池等信息。
每个 PyFunctionObject 都拥有有一个独立的常量池,如果在同一个 PyFunctionObject 里创建了值相同的常量,那么这些常量只会在常量池里出现一份。也就是说位于顶层的变量 c、d 和位于 foo 函数中 e、f 实际上都分别引用了来自同一个 PyFunctionObject 的常量池中的内存对象,所以变量 c/d、e/f 的内存地址才会两两相同。同理,因为变量 c 和 e 分别存在于两个不同的 PyFunctionObject 中,所以即便两者的值相同,也不是同一个内存对象。

需要注意的是这里提到的「常量」,通常指的是整数类型对象。又因为整型中的小整数具有小整数缓存池机制,所以即便是在不同的 PyFunctionObject 中,小整数变量也依旧会引用同一个内存对象。

逐行解释

在交互式解释器中执行 Python 代码,对应面试题中在 IPython 中运行的代码。每输入一行语句就会立即执行,所以此时的「编译单元」为一行语句。注意这里所说的“一行”指的是一次完整性输入,例如:

In [33]: c = 257In [34]: d = 257In [35]: def foo():...:     e = 257...:     f = 257...:...:     print('a is b: %s' % (a is b))...:     print('c is d: %s' % (c is d))...:     print('c is e: %s' % (c is e))...:     print('e is f: %s' % (e is f))...:

上述代码块实际上属于 3 次完整性输入,分别得到了 3 个不同的 PyFunctionObject,所以变量 c、d 自然也就不存在于同一个常量池中,所以 (c is d) == False

最后

实际上这一个看是并没有什么卵用的知识点,掌握与否并不会影响到日常的编程任务。但往往是这种“大隐隐与市”的知识点,最能区别出开发者对一门语言的理解,以及开发者是否具有专研精神的考量。
其次,我们能通过 Python 对整数实现的优化得到一些启发,就是 pool 的设计与机制是一种能够降低应用系统中性能损耗的有效手段

Python 笔试集(2):你不知道的 Python 整数相关推荐

  1. Python 笔试集(4):True + True == ?

    目录 目录 前文列表 面试题True Ture 布尔值 布尔类型是特殊的整数类型 前文列表 Python 笔试集:什么时候 i = i + 1 并不等于 i += 1? Python 笔试集(1):关 ...

  2. python经典程序实例-你不知道的Python语言的经典五大案例

    原标题:你不知道的Python语言的经典五大案例 身为全国三大主流编程语言之一,Python是一种面向对象的解释型计算机程序设计语言,具有丰富和强大的库.Python的简单入门及其它的全能型,能适应所 ...

  3. python历年来经典项目实例-你不知道的Python语言的经典五大案例

    原标题:你不知道的Python语言的经典五大案例 身为全国三大主流编程语言之一,Python是一种面向对象的解释型计算机程序设计语言,具有丰富和强大的库.Python的简单入门及其它的全能型,能适应所 ...

  4. Python 笔试集(1):关于 Python 链式赋值的坑

    前言 Python 的链式赋值是一种简易型批量赋值语句,一行代码即可为多个变量同时进行赋值. 例如: x = y = z = 1 链式赋值是一种非常优雅的赋值方式,简单.高效且实用.但同时它也是一个危 ...

  5. Python 笔试集:什么时候 i = i + 1 并不等于 i += 1?

    ​​增强型赋值语句是经常被使用到的,因为从各种学习渠道中,我们能够得知 i += 1 的效率往往要比 i = i + 1 更高一些(这里以 += 为例,实际上增强型赋值语句不仅限于此).所以我们会乐此 ...

  6. python 10个100以内随机整数编辑_Python基础语法合集(转自CSDN)

    了解python 1. 了解Python Python是一种解释型(这意味着开发过程中没有了编译这个环节).面向对象(支持面向对象的风格或代码封装在对象的编程技术).动态数据类型的交互式(可在命令行中 ...

  7. Python 笔试面试合集

    Python 常见的 170 道面试题全解析:2019 版 全面剖析 Python 面试知识点 Python 全栈工程师必备面试题 300 道(2020 版) 2019 年最全 Python 面试题汇 ...

  8. 【PTA|Python】浙大版《Python 程序设计》题目集:第六章

    前言 Hello!小伙伴! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出-   自我介绍 ଘ(੭ˊᵕˋ)੭ 昵称:海轰 标签:程序猿|C++选手|学生 简介:因C语言结识编程,随后转入计 ...

  9. python笔试110题(Interview questions)

    python笔试110题–详解 一行代码实现1-100之和 解: print(sum(range(1, 101))) 如何在一个函数内部修改全局变量 解: 1)函数内部用global声明,global ...

最新文章

  1. Magento中如何在模块中使用多张数据表并配置多个model?
  2. html文件div盒子浮动,css基础03-盒模型、网站布局思想、浮动以及浮动带来的影响...
  3. openresty开发系列26--openresty中使用redis模块
  4. 第五天学习Java的笔记(if,switch顺序结构)
  5. 解决java.lang.NoClassDefFoundError: org/aopalliance/intercept/MethodInterceptor问题
  6. 大数据学习笔记:Hadoop生态系统
  7. (39)System Verilog程序Program设计实例
  8. AndroidStudio_android实现双击_3击_监听实现---Android原生开发工作笔记240
  9. 【Java从0到架构师】Redis 进阶 - 持久化(RBD、AOF)、高可用(主从复制、哨兵机制、Cluster)
  10. mobile兼容性调整,根据rem,字体大小,视窗宽度
  11. 190313每日一句
  12. C# 纯真数据库读取
  13. ourdev 学习arm先看看,工具和软件分析
  14. JavaScript----事件
  15. 基于python爬虫下载网站在线视频
  16. yum源及常用安装包整理
  17. 淘宝线上线下“出淘”欲打造零售业航母
  18. 在Idea解决找不到sun.misc.BASE64Encoder及sun.misc.BASE64Decoder找不到包
  19. 数据结构和算法学习指南
  20. Fast Online Object Tracking and Segmentation: A Unifying Approach

热门文章

  1. Xamarin iOS开发实战上册(内部资料daxueba.net)
  2. python3扫雷代码_GitHub - pantaduce/minesweeper: Python代码编写的扫雷游戏
  3. wpf里的menu怎么用_股市里的两市成交量是什么,它反映了什么,我是怎么用它来定投的...
  4. 军事科学院医学研究院认知与脑科学研究团队2021年诚聘启事(更新)
  5. 7分钟分析人类全基因组,他们刷新全球纪录,此前最快也要24小时
  6. 魔改宜家灯泡当主机,玩转《毁灭战士》无压力!网友:远超我家第一台电脑...
  7. 马斯克刚失了一枚大火箭!
  8. 如何提高PyTorch“炼丹”速度?这位小哥总结了17种方法,可直接上手更改的那种...
  9. AI也能写高考作文?我们用清华刚刚开源的「九歌」试了试
  10. 外部情况错综复杂,联想靠什么过「久日子」