问题一:列表和元组的内部实现

第一个问题,是胡峣同学提出的,有关列表(list)和元组(tuple)的内部实现,想知道里边是 linked list 或 array,还是把 array linked 一下这样的方式?

关于这个问题,我们可以分别从源码来看。

先来看 Python 3.7 的 list 源码。你可以先自己阅读下面两个链接里的内容。

listobject.h:https://github.com/python/cpython/blob/949fe976d5c62ae63ed505ecf729f815d0baccfc/Include/listobject.h#L23

listobject.c: https://github.com/python/cpython/blob/3d75bd15ac82575967db367c517d7e6e703a6de3/Objects/listobject.c#L33

我把 list 的具体结构放在了下面:

可以看到,list 本质上是一个 over-allocate 的 array。其中,ob_item 是一个指针列表,里面的每一个指针都指向列表的元素。而 allocated 则存储了这个列表已经被分配的空间大小。

需要注意的是,allocated 与列表实际空间大小的区别。列表实际空间大小,是指 len(list) 返回的结果,即上述代码注释中的 ob_size,表示这个列表总共存储了多少个元素。实际情况下,为了优化存储结构,避免每次增加元素都要重新分配内存,列表预分配的空间 allocated 往往会大于 ob_size(详见正文中的例子)。

所以,它们的关系为:allocated >= len(list) = ob_size

如果当前列表分配的空间已满(即 allocated == len(list)),则会向系统请求更大的内存空间,并把原来的元素全部拷贝过去。列表每次分配空间的大小,遵循下面的模式:

0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...

我们再来分析元组。下面是 Python 3.7 的 tuple 源码,同样的,你可以先自己阅读一下。

tupleobject.h: https://github.com/python/cpython/blob/3d75bd15ac82575967db367c517d7e6e703a6de3/Include/tupleobject.h#L25

tupleobject.c:https://github.com/python/cpython/blob/3d75bd15ac82575967db367c517d7e6e703a6de3/Objects/tupleobject.c#L16

同样的,下面为 tuple 的具体结构:

你可以看到,它和 list 相似,本质也是一个 array,但是空间大小固定。不同于一般 array,Python 的 tuple 做了许多优化,来提升在程序中的效率。

举个例子,当 tuple 的大小不超过 20 时,Python 就会把它缓存在内部的一个 free list 中。这样,如果你以后需要再去创建同样的 tuple,Python 就可以直接从缓存中载入,提高了程序运行效率。

问题二:为什么在旧哈希表中,元素会越来越稀疏?

第二个问题,是 Hoo 同学提出的,为什么在旧哈希表中,元素会越来越稀疏?

我们可以先来看旧哈希表的示意图:

--+-------------------------------+| 哈希值 (hash)  键 (key)  值 (value)
--+-------------------------------+
0 |    hash0      key0    value0
--+-------------------------------+
1 |    hash1      key1    value1
--+-------------------------------+
2 |    hash2      key2    value2
--+-------------------------------+
. |           ...
__+_______________________________+

你会发现,它是一个 over-allocate 的 array,根据元素键(key)的哈希值,来计算其应该被插入位置的索引。

因此,假设我有下面这样一个字典:

{'name': 'mike', 'dob': '1999-01-01', 'gender': 'male'}

那么这个字典便会存储为类似下面的形式:

entries = [
['--', '--', '--']
[-230273521, 'dob', '1999-01-01'],
['--', '--', '--'],
['--', '--', '--'],
[1231236123, 'name', 'mike'],
['--', '--', '--'],
[9371539127, 'gender', 'male']
]

这里的’---‘,表示这个位置没有元素,但是已经分配了内存。

我们知道,当哈希表剩余空间小于 1/3 时,为了保证相关操作的高效性并避免哈希冲突,就会重新分配更大的内存。所以,当哈希表中的元素越来越多时,分配了内存但里面没有元素的位置,也会变得越来越多。这样一来,哈希表便会越来越稀疏。

而新哈希表的结构,改变了这一点,也大大提高了空间的利用率。新哈希表的结构如下所示:

Indices
----------------------------------------------------
None | index | None | None | index | None | index ...
----------------------------------------------------Entries
--------------------
hash0   key0  value0
---------------------
hash1   key1  value1
---------------------
hash2   key2  value2
---------------------...
---------------------

你可以看到,它把存储结构分成了 Indices 和 Entries 这两个 array,而’None‘代表这个位置分配了内存但没有元素。

我们同样还用上面这个例子,它在新哈希表中的存储模式,就会变为下面这样:

indices = [None, 1, None, None, 0, None, 2]
entries = [
[1231236123, 'name', 'mike'],
[-230273521, 'dob', '1999-01-01'],
[9371539127, 'gender', 'male']
] 

其中,Indices 中元素的值,对应 entries 中相应的索引。比如indices中的1,就对应着entries[1],即’'dob': '1999-01-01'‘

对比之下,我们会清晰感受到,新哈希表中的空间利用率,相比于旧哈希表有大大的提升。

问题三:有关异常的困扰

第三个问题,是“不瘦到 140 不改名”同学提出的,对“NameError”异常的困惑。这是很常见的一个错误,我在这里也解释一下。

这个问题其实有点 tricky,如果你查阅官方文档,会看到这么一句话”When an exception has been assigned using as target, it is cleared at the end of the except clause. ”

这句话意思是,如果你在异常处理的 except block 中,把异常赋予了一个变量,那么这个变量会在 except block 执行结束时被删除,相当于下面这样的表示:

e = 1
try:1 / 0
except ZeroDivisionError as e:try:passfinally:del e 

这里的 e 一开始指向整数 1,但是在 except block 结束时被删除了(del e),所以程序执行就会抛出“NameError”的异常。

因此,这里提醒我们,在平时写代码时,一定要保证 except 中异常赋予的变量,在之后的语句中不再被用到。

问题四:关于多态和全局变量的修改

最后的问题来自于 farFlight 同学,他提了两个问题:

  1. Python 自己判断类型的多态和子类继承的多态 Polymorphism 是否相同?
  2. 函数内部不能直接用 += 等修改全局变量,但是对于 list 全局变量,却可以使用 append、extend 之类修改,这是为什么呢?

我们分别来看这两个问题。对于第一个问题,要搞清楚多态的概念,多态是指有多种不同的形式。因此,判断类型的多态和子类继承的多态,在本质上都是一样的,只不过你可以把它们理解为多态的两种不同表现。

再来看第二个问题。当全局变量指向的对象不可变时,比如是整型、字符串等等,如果你尝试在函数内部改变它的值,却不加关键字 global,就会抛出异常:

x = 1def func():x += 1
func()
x## 输出
UnboundLocalError: local variable 'x' referenced before assignment 

这是因为,程序默认函数内部的 x 是局部变量,而你没有为其赋值就直接引用,显然是不可行。

不过,如果全局变量指向的对象是可变的,比如是列表、字典等等,你就可以在函数内部修改它了:

x = [1]def func():x.append(2)
func()
x## 输出
[1, 2]

当然,需要注意的是,这里的x.append(2),并没有改变变量 x,x 依然指向原来的列表。事实上,这句话的意思是,访问 x 指向的列表,并在这个列表的末尾增加 2。

今天主要回答这些问题,同时也欢迎你继续在留言区写下疑问和感想,我会持续不断地解答。希望每一次的留言和答疑,都能给你带来新的收获和价值

转载于:https://www.cnblogs.com/geogre123/p/11398997.html

景霄讲解Python部分内部实现相关推荐

  1. python函数设置默认参数_深入讲解Python函数中参数的使用及默认参数的陷阱

    这篇文章主要介绍了Python函数中参数的使用及默认参数的陷阱,文中将函数的参数分为必选参数.默认参数.可变参数和关键字参数来讲,要的朋友可以参考下 C++里函数可以设置缺省参数,Java不可以,只能 ...

  2. python中输入字符串_简单讲解Python中的字符串与字符串的输入输出

    简单讲解Python中的字符串与字符串的输入输出 发布于 2016-03-26 14:35:42 | 110 次阅读 | 评论: 0 | 来源: 网友投递 Python编程语言Python 是一种面向 ...

  3. python免费课程讲解-Python零基础免费入门课程

    小白在网上找的其他的免费课程,感觉还是很难,因为他们针对的不是"零基础"的学生,真正的零基础是什么?就是只会上网,接触过电脑,而不是网上其他课程"设定的电脑科班的&quo ...

  4. 编程python爬取网页数据教程_实例讲解Python爬取网页数据

    一.利用webbrowser.open()打开一个网站: >>> import webbrowser >>> webbrowser.open('http://i.f ...

  5. python中能够处理的最大整数是_实例讲解Python中整数的最大值输出

    在Python中可以存储很大的值,如下面的Python示例程序: x = 10000000000000000000000000000000000000000000; x = x + 1 print ( ...

  6. python声明匿名函数_举例讲解Python的lambda语句声明匿名函数的用法

    所谓匿名函数,即是不需要定义函数,像表达式一样使用,不需要函数名(很多时候名字让我很困扰),一些简单的函数简单化, 举个例子 我需要两个整数相加的函数,通常是这么定义的def add(x, y): r ...

  7. python100例详解-几个小例子给你讲解Python中类的描述符

    原标题:几个小例子给你讲解Python中类的描述符 学习 Python 这么久了,说起 Python 的优雅之处,能让我脱口而出的, Deor(描述符)特性可以排得上号. 描述符是Python 语言独 ...

  8. 【机器学习】【隐马尔可夫模型-3】后向算法:算法详解+示例讲解+Python实现

    0.前排提示 csdn有些数学公式编辑不出来,所以本博用容易书写的表达式来表示专业数学公式,如: (1)  在本博客中用α<T>(i)来表示 (2)在本博客中用[i=1, N]∑来表示 注 ...

  9. python 系统管理_实例讲解python用户管理系统

    本文主要为大家分享一篇python用户管理系统的实例讲解,具有很好的参考价值,希望对大家有所帮助.一起跟随小编过来看看吧,希望能帮助到大家. 自定义函数+装饰器,每一个模块写的一个函数 很多地方能用装 ...

最新文章

  1. SLAM之特征匹配(二)————RANSAC--------翻译以及经典RANSAC以及其相关的改进的算法小结
  2. (理论篇)从基础文件IO说起虚拟内存,内存文件映射,零拷贝
  3. vmtouch--the Virtual Memory Toucher
  4. Verilator简介及其下载安装卸载
  5. ionic安装及入门示例
  6. pytorch datasets.ImageFolder,DataLoader形成的tensor是什么样的?
  7. electron-vue解决打包错误、无法调用出开发者工具(亲测有效)
  8. Apache PIO 操作Excel
  9. MSSQL调优实战一 乱建聚集索引的后果
  10. 微软云计算介绍与实践(实践之一)
  11. H3C交换机配件RS232配置线(DB9针转RJ45)
  12. 重大建设项目土地勘测定界涉及基本农田怎么计算
  13. 面向猴子编程 GO制作水印
  14. C++——std::Priority_queue
  15. Leetcode 5773:插入后的最大值
  16. 石家庄地铁售票系统源代码
  17. SkipList(跳跃表)详解
  18. arduino学习笔记十四--Arduino 环境光线传感器实验
  19. 互联网如何再定义古老的眼镜行业?
  20. 2021年江西省研究生数学建模竞赛题目(一)题目:某肿瘤疾病诊疗的经济学分析

热门文章

  1. BZOJ 3224 Treap
  2. vue-cli起服务chrome不能打断点——每周汇总(第一周)
  3. 邻接矩阵有向图的介绍
  4. Drupal basic
  5. IOS启程06—iOS设置圆角图片
  6. 透视前端工程化之 Webpack 基本介绍【文末有彩蛋~】
  7. 用 State Pattern 来实现一个简单的 状态机
  8. Loadrunner11完美破解小笔记
  9. HTML5 canvas绘制雪花飘落动画(需求分析、知识点、程序编写分布详解)
  10. [蓝桥杯历届试题] 国庆星期日