准备回顾一下python源代码,不过不准备说的太细,尽量勾勒框架,不引用代码。

python中所有东西都是对象,进一步地,这些对象可以分为类型对象(type)or实例对象,有时一个对象即可以是类型,也可以是实例。所有这些对象中,除了内置的类型对象外,别的都生存于堆上,内置的类型对象则静态分配内存。

每个对象头部都有一个PyObject_HEAD(其实对于某些需要被gc管理的对象,它的头部先为PyGC_Head,再为PyObject_HEAD)。变长对象在HEAD后还有一个ob_size表示变长对象元素个数的多少,非字节数。

类型的信息都在它的type对象里,源码中为struct _typeobject,也就是PyTypeObject。比如实例化一个类型,那会先找它的tp_new(找不到的话在父类找),在tp_new中根据该type的tp_basesize进行分配内存,再调用tp_init进行初始化。对类型的实例做运算,比如相加其实也是找type对象中相应的函数指针。type对象中的信息到后来基本都会在类型的dict中和相应的key对应起来。

下面分析具体的类型。

int:比较简单,关键在于如何高效地实现。python首先有小整数对象。默认在[-5, 257)。如果超出范围则使用通用的缓冲池,对于大整数则有PyIntBlock,用来作缓冲池。一个block大小大概为1000个字节,去掉头部(8字节),可以存82个整数对象。block之间通过指针相连,首指针为block_list,free_list则维护着一条可以链表,free_list链表的下一项由未用的PyIntObject的ob_type来维持。

一些细节:当无可以用缓冲池可用时python会调用fill_free_list来创建一个新的block,并将其插入block_list,再把free_list指向这个block的objects中的最后一个元素。当某个block中的某个int被释放时,它将自己的ob_type指向free_list,并修改free_list等于它的地址,其实就是一个头部插入,这样把多个block间的objects数组联系起来防止出现内存泄漏。一个值得注意的地方是小整数对象池其实也是生活在block里面,在是整个python环境初始化的时候生成。这里可以看出,为int分配的内存是永远也不会被python释放的,所有的int对象使用的内存大小和同时存在的int数量的最大值有关。

string:复杂一些,变长对象。对变长对象内存的管理。每个string对象除了头部外还保存了hash值(ob_shash,避免重复计算,初始-1)、是否已经被intern机制处理过(ob_sstate)、指向实际内存的指针(ob_sval),ob_sval指向的应该是一段ob_size+1长度的内存(为了兼容C,字符串要以'\0'结尾)。在从char *创建string时还是比较直接的,就是检查一些边界情况、初始化hash等,最后逐个拷贝char。python中有一个nullstring指向空字符串,通过intern机制共享,所以不会同时存在多个空字符串。

传统缓冲池。相当于int小整数缓冲池,对单个的char,python也会维持一个缓冲池。创建单个char的string时,如果缓冲池里已有,则直接返回。如果没有,根据char创建string,再对它进行intern,再存入缓冲池。

intern机制。python会维持一个dict,用来保存当前已经被创建的string,如果新创建的string已经在这个dict,也就是已经被intern机制处理过了,那么就会直接返回dict中的值。也就是说一般两个相同的字符串的id是相同的。要注意的是,无论字符串有没有存在于这个dict中,python都会创建一个新要string,原因是因为保存在dict中只能是PyObject,因此肯定要创建一个python对象。intern后的string有两种状态,mortal和immortal,区别在于后者永远不会被gc回收。

要提到的是,创建string时使用的是PyObject_Malloc开头的分配函数,一般来讲它不会每次都从os分配内存,而是从python维持的一个内存池中分配。

list:不仅是变长对象,还是可变对象。在变长头部之外PyListObject还保存了一个PyObject **ob_item指针,一个int allocated。ob_item就是指向实际成员的指针,allocated代表了list当前申请的内存能装多少个PyObject,变长头内的ob_size则代表list中已有多少个PyObject。当创建一个list时,需要指定list的大小(参数size),要申请的内存可以分为两部分,一个是list本身,一个是指向成员变量的指针。如果list缓冲池可用(numfree > 0),那就从缓冲池中给list分配一块内存,否则使用PyObject_GC_NEW来分配空间(和string不同,因为list中的成员可以指向其它python对象,这个函数和python垃圾回收的三色标记法有关)。然后再根据size大小分配一段连续的内存来保存指向各个成员的指针,新创建的list中的ob_size和allocated都为size。

创建list后给list里的某个位置赋值比较简单,就是简单的設定指针而已。添加操作要复杂一些,首先会调整list的大小,使得allocate>=size>=allocate/2。如果该等式已经满足,那么不更改list大小,如果不满足的话通过new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6) + newsize得出新的allocated大小,再通过PyMem_Resize来调整保存成员指针的内存。resize后则使用最简单的策略移动从插入到结尾的成员指针,再设置该位置上的成员。删除成员时实际删除后的也要进行resize操作,和插入时类似。实际删除的操作则由list_ass_slice来实现,它有两种用法,一是更改list中的某一段为特定的值,另一种就是删除list中的某一段。list中每删除一个元素都会造成内存拷贝。一个值得注意的细节是由于从list中删除或是更改的对象需要减少引用计数,但减少引用计数时又会循环调用一些list函数,可能会造成list索引值的破坏,因而函数中得用一个temp数组保留从list中剥离的成员,等删除工作完成,list结构已经确定的情况下再逐一减少其引用。

当list被销毁时,对其成员逐个减少引用,然后检查缓冲区是否已经满,如果没有的话就将该list放入缓冲区,这样下次再创建list的时候就不用为list对象本身再分配对象了。

dict:python中的dict是用hash表实现,解决冲突的方法是开放定址法,即二次探测,删除时使用伪删除技术(顺便说下在一致性假设下,如果用k来表示hash表的使用率的话,那么一次成功查找需要的比较次数为1/k * ln(1/(1-k)),插入时的比较次数最多为1/(1-k))。

在一个dict中,每个(key,value)对组成一个entry,entry中还另外存储了key的hash值。对每个entry来说有三种状态unused(key,value皆为NULL,初始状态),active(key,value不为NULL,存储了元素),dump(对应于伪删除技术,key=dummy,value=NULL)。dict实际上就是entry的集合,定义如下:typedef struct _dictobject PyDictObject;

struct _dictobject {

PyObject_HEAD

Py_ssize_t ma_fill;  /* # Active + # Dummy */

Py_ssize_t ma_used;  /* # Active */

/* The table contains ma_mask + 1 slots, and that's a power of 2.

* We store the mask instead of the size because the mask is more

* frequently needed.

*/

Py_ssize_t ma_mask;

/* ma_table points to ma_smalltable for small tables, else to

* additional malloc'ed memory.  ma_table is never NULL!  This rule

* saves repeated runtime null-tests in the workhorse getitem and

* setitem calls.

*/

PyDictEntry *ma_table;

PyDictEntry *(*ma_lookup)(PyDictObject *mp, PyObject *key, long hash);

PyDictEntry ma_smalltable[PyDict_MINSIZE];

};

PyDict_MINSIZE一般被设为8。使用PyDict_New来创建一个新dict时,其实就是分配相应的内存,将ma_fill,ma_used设为0,ma_mask设为7(8-1),ma_table指向ma_smalltable,ma_lookup默认为lookdict_string。在实现dict时python也使用了缓冲池,方法和list基本一样。

ma_lookup这个函数指针确定了散裂函数和冲突发生时的二次散裂函数,在dict中进行搜索有两种方法,lookdict_string和lookdict,后一种是更一般的方法。由于python中dict用的非常广泛,而这些dict中大多数key都是string,因而专门提供了一个搜索方法来进行加速,其实两种方法的本质都是一样的。搜索的具体过程如下:1. 获得探测链中当前应该检测的entry;2. 如果是unused状态,那搜索失败,应该返回一个可用的(可以被设置值)的entry,所以如果freeslot不为空(之前找到了dummy态的entry)就返回freeslot指向的entry,否则返回当前entry;3. 如果entry为dummy态且freeslot未设置,则设置freeslot,继续查找下一个;4. 依次检查当前entry的key和查找的key是否引用相同、值相同,若不是继续查找下一个。需要注意的是,dict使用哪种方法进行查找取决于待查找的key,如果是string的话则利用lookdict_string,和本身已经有的entry中的key无关。

dict的插入和删除基于之前的搜索,很好理解。当插入时先搜索该key,得到一个entry,再根据它的状态来修改它达到插入的上目的。删除操作也类似。需要注意的是插入元素的时候,会检查ma_fill/(ma_mask+1)是否大于2/3,如果装载率的确大于这个值并且有unused或dummy的entry被填充的时候,就会调整dict的大小,新的大小最小为minsize=ma_userd*(ma_used>50000?2:4),显然改变dict大小会造成内存移动,因此这时候可以丢弃dummy的entry。新的dict大小从8开始逐次*2增长,直到大于minsize。接下来就是一些初始化动作,逐个检查entry并插入新的dict等。

python内置对象的实现_Python 内置对象的实现相关推荐

  1. python内置对象是什么_Python内置对象类型之数字类型

    Python中有6种内置对象类型 整数.浮点数–Number 字符串–String 列表–List 元组–Tuple 字典–Dictionary 集合–Set 不可变类型:Number.String. ...

  2. python内置函数下划线_python(内置函数)

    检测本文件中对象是否存在 # -*- coding: utf-8 -*- import sys x=10 obj1=sys.modules[__name__] print(hasattr(obj1,& ...

  3. python内置函数绝对值图像_Python内置函数

    内置函数清单 1.abs()     求数字的绝对值 2.all()    判断集合元素是否都为True,都为True时返回True,否则返回False. 3.any()    判断集合元素是否都为F ...

  4. 不是python内置函数的是_Python内置函数

    print(abs(2)) #求出绝对值 print(all(([],1,3))) #传1个可跌倒对象,元素中包含的全部为可迭代对象,返回True 其他返回False #如果可迭代对象是空,就返回Tr ...

  5. python中的引用怎么理解_python 引用和对象理解

    今天浏览博客的时候看到这么一句话: python中变量名和对象是分离的:最开始的时候是看到这句话的时候没有反应过来.决定具体搞清楚一下python中变量与对象之间的细节.(其实我感觉应该说 引用和对象 ...

  6. python一切都是对象 知乎_python一切皆对象

    1. 一切皆对象 1.1 先了解类 故事要从一切皆对象开始讲起,与对象相伴的一个概念叫类,类就好比模板,对象都是根据这个模板刻画衍生出来的. 比如下面这行简单的代码 a = int('3') prin ...

  7. python迭代对象是什么意思_python的迭代对象

    迭代对象这个概念是python里面非常核心的东西,我想跟大家分享一下,什么是可迭代对象. 比如我们有一个list1=[1,2,3],s='abc' for n in list1: print n fo ...

  8. python中不可迭代对象有哪些_python可迭代对象

    本身实现了迭代方法的对象称之为可迭代对象,可迭代对象特点: 支持每次返回自己所包含的一个成员的对象: 对象实现了 __iter__ 方法: 所有数据结构都是可迭代对象: for 循环要求对象必须是一个 ...

  9. python可迭代对象 迭代器生成器_Python可迭代对象、迭代器和生成器

    8.1 可迭代对象(Iterable) 大部分对象都是可迭代,只要实现了__iter__方法的对象就是可迭代的. __iter__方法会返回迭代器(iterator)本身,例如: >>&g ...

最新文章

  1. javascript之iframe
  2. 基于redis的分布式锁解析
  3. flask的同一ip域名不同端口的两个网站session冲突
  4. 【小白学习C++ 教程】八、在C++指针传递引用和Const关键字
  5. LeetCode 1966. Binary Searchable Numbers in an Unsorted Array
  6. xshell利用linux rz sz 命令上传、下载文件
  7. pip3 安装pycrypto 时报错
  8. iproute2 对决 net-tools
  9. Linux firewall防火墙详解(二)——firewall配置
  10. 谷歌大脑阿尔伯塔联合发表:离线强化学习的优化视角【附代码】
  11. 实现Windows访问Linux文件系统
  12. 鼠标被计算机无法识别怎么办,win7系统usb鼠标无法识别怎么解决 电脑usb鼠标无法识别解决方法...
  13. mysql导出数据到csv文件
  14. Ubuntu20.04安装输入法
  15. tp3.2中前台模板中日期时间的转换
  16. win7网络共享连接计算机需要密码,win7系统共享打印机需要用户名和密码的解决办法...
  17. 【Hive】解析复杂json格式字段
  18. 英国AI医疗保健初创企业snap40获800万美元融资
  19. FFT快速傅里叶变换 超详细的入门学习总结
  20. 20款国内外免费使用 主流杀毒软件

热门文章

  1. Delphi - 对象构造浅析后续
  2. php ppt转换成图片格式,帮忙看下,这个程序实现使用office的COM组件将ppt转换为图片...
  3. 1. 列维过程的混沌及可料表示(2)
  4. 1. 列维过程的混沌及可料表示(1)
  5. 【测试】用示波器抓取红外遥控器NEC信号
  6. 0.项目运行环境和项目经理
  7. Android Java 8使用Lamda报错:Execution failed for task :app:transformJackWithJackForDebug - Android?...
  8. scrapy proxy and user_agent
  9. 十个有用的linux命令行技巧
  10. Visual Studio 11 Beta 带来新的Metro 应用开发体验