Python 笔试集(2):你不知道的 Python 整数
面试题
分别给出下述代码在终端(e.g. IPyhon)中和在程序中的运行结果:
a = 256
b = 256
c = 257
d = 257
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))
foo()
- IPython 中运行的结果:
In [31]: a = 256
In [32]: b = 256
In [33]: c = 257
In [34]: d = 257
In [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.py
a 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 = 256
In [26]: b = 256
In [27]: a is b
Out[27]: True
In [28]: c = 257
In [29]: d = 257
In [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 = 257
In [34]: d = 257
In [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 整数的更多相关文章
- Python 笔试集(4):True + True == ?
目录 目录 前文列表 面试题True Ture 布尔值 布尔类型是特殊的整数类型 前文列表 Python 笔试集:什么时候 i = i + 1 并不等于 i += 1? Python 笔试集(1):关 ...
- Python 笔试集(3):编译/解释?动态/静态?强/弱?Python 是一门怎样的语言
面试题 解释/编译?动态/静态?强/弱?Python 到底是一门怎样的语言? 编译 or 解释? 编译.解释都是指将(与人类亲和的)编程语言翻译成(计算机能够理解的)机器语言(Machine code ...
- Python 笔试集(1):关于 Python 链式赋值的坑
前言 Python 的链式赋值是一种简易型批量赋值语句,一行代码即可为多个变量同时进行赋值. 例如: x = y = z = 1 链式赋值是一种非常优雅的赋值方式,简单.高效且实用.但同时它也是一个危 ...
- Python 笔试集:什么时候 i = i + 1 并不等于 i += 1?
增强型赋值语句是经常被使用到的,因为从各种学习渠道中,我们能够得知 i += 1 的效率往往要比 i = i + 1 更高一些(这里以 += 为例,实际上增强型赋值语句不仅限于此).所以我们会乐此 ...
- 『Python题库 - 填空题』151道Python笔试填空题
『Python题库 - 填空题』Python笔试填空题 part 1. Python语言概述和Python开发环境配置 part 2. Python语言基本语法元素(变量,基本数据类型, 基础运算) ...
- python垃圾回收机制与小整数池
python垃圾回收机制 当引用计数为0时,python会删除这个值. 引用计数 x = 10 y = x del x print(y) 10 引用计数+1,引用计数+1,引用计数-1,此时引用计数为 ...
- 实现Redis Cluster并实现Python链接集群
目录 一.Redis Cluster简单介绍 二.背景 三.环境准备 3.1 主机环境 3.2 主机规划 四.部署Redis 4.1 安装Redis软件 4.2 编辑Redis配置文件 4.3 启动R ...
- 你不知道的Python容器
你不知道的Python容器 你不知道的Python容器 散列表 ChainMap MappingProxyType 线性表 堆 参考资料 昨天阅读了<Python Tricks: The Boo ...
- 有哪些你不知道的python小工具
python作为越来越流行的一种编程语言,不仅仅是因为它语言简单,有许多现成的包可以直接调用. python中还有大量的小工具,让你的python工作更有效率. 1.- 快速共享 - HTTP服务器 ...
随机推荐
- help_topic表,以字符拆分,一行转多行
help_topic表是数据库mysql下的一个表 SUBSTRING_INDEX(s, delimiter, number) 返回从字符串 s 的第 number 个 ...
- 异步分布式队列Celery
异步分布式队列Celery 转载地址 Celery 是什么? 官网 Celery 是一个由 Python 编写的简单.灵活.可靠的用来处理大量信息的分布式系统,它同时提供操作和维护分布式系统所需的工具 ...
- Linux之RedHat7如何更换yum源
目前,我们常见的系统大概就是Windows.Linux和Mac OS了.Windows系统应该是大部分人最早开始接触的系统,毕竟Windows系统使用起来相当方便,只需要点点鼠标,外加会简单的打字,一 ...
- python中英文翻译模块
从一种语言到另一种语言的文本翻译在各种网站中越来越普遍. 帮助我们执行此操作的python包称为translate. 可以通过以下方式安装此软件包. 它提供主要语言的翻译. 官网:https://py ...
- [bzoj4358]permu:莫队+线段树/回滚莫队
这道题是几天前水过去的,现在快没印象了,水一发. 首先我们看到它让求解的是最长的值域 连续段长度,很好. 然后就想到了山海经,但但是我还没有做. 然后又想到了很久以前的一次考试的T3旅馆hotel(我 ...
- Ubuntu启动器创建
Ubuntu 启动器创建 启动器的本质是一个后缀为.desktop的文件,文件内容如下(这里为我创建的Chrome启动器) [Desktop Entry] Encoding=UTF- Name=Chr ...
- Spring 跨重定向请求传递数据
在处理完POST请求后, 通常来讲一个最佳实践就是执行一下重定向.除了其他的一些因素外,这样做能够防止用户点击浏览器的刷新按钮或后退箭头时,客户端重新执行危险的POST请求. 在控制器方法返回的视图名 ...
- NOIP2015普及组总结
NOIP2015普及组总结 这次考试总体感觉不错,不过觉得时间有点紧,在最后30分钟才打完. 第一题(金币coin):大大的W!爆搜O(N),一分钟打完: 第二题(扫雷游戏mine):同上: 第三题( ...
- jvm——NIO
https://blog.csdn.net/Evankaka/article/details/48464013 https://www.cnblogs.com/aspirant/p/9166944.h ...
- 如何用 Redis 统计独立用户访问量
众所周至,拼多多的待遇也是高的可怕,在挖人方面也是不遗余力,对于一些工作3年的开发,稍微优秀一点的,都给到30K的Offer,当然,拼多多加班也是出名的,一周上6天班是常态,每天工作时间基本都是超过1 ...