python的整数字符串列表字典对象的实现原理
转自http://foofish.net/index.html
==========================================================================================================
整数对象在Python内部用PyIntObject
结构体表示:
typedef struct { PyObject_HEAD long ob_ival; } PyIntObject;
PyObject_HEAD宏中定义的两个属性分别是:
int ob_refcnt; struct _typeobject *ob_type;
这两个属性是所有Python对象固有的:
- ob_refcnt:对象的引用计数,与Python的内存管理机制有关,它实现了基于引用计数的垃圾收集机制
- ob_type:用于描述Python对象的类型信息。
由此看来PyIntObject就是一个对C语言中long类型的数值的扩展,出于性能考虑,对于小整数,Python使用小整数对象池small_ints
缓存了[-5,257)之间的整数,该范围内的整数在Python系统中是共享的。
#define NSMALLPOSINTS 257 #define NSMALLNEGINTS 5 static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
而超过该范围的整数即使值相同,但对象不一定是同一个,如下所示:当a与b的值都是10000,但并不是同一个对象,而值为1的时候,a和b属于同一个对象。
>>> a = 10000 >>> b = 10000 >>> print a is b False >>> a = 1 >>> b = 1 >>> print a is b True
对于超出了[-5, 257)之间的其他整数,Python同样提供了专门的缓冲池,供这些所谓的大整数使用,避免每次使用的时候都要不断的malloc分配内存带来的效率损耗。这块内存空间就是PyIntBlock
。
struct _intblock { struct _intblock *next; PyIntObject objects[N_INTOBJECTS]; }; typedef struct _intblock PyIntBlock; static PyIntBlock *block_list = NULL; static PyIntObject *free_list = NULL;
这些内存块(PyIntBlock)通过一个单向链表组织在一起,表头是block_list
,表头始终指向最新创建的PyIntBlock对象。
PyIntBlock有两个属性:next,objects。next指针指向下一个PyIntBlock对象,objects是一个PyIntObject数组(最终会转变成单向链表),它是真正用于存储被缓存的PyIntObjet对象的内存空间。
free_list
单向链表是所有PyIntBlock内存块中空闲的内存。所有空闲内存通过一个链表组织起来的好处就是在Python需要新的内存来存储新的PyIntObject对象时,能够通过free_list
快速获得所需的内存。
创建一个整数对象时,如果它在小整数范围内,就直接从小整数缓冲池中直接返回,如果不在该范围内,就开辟一个大整数缓冲池内存空间:
[intobject.c] PyObject* PyInt_FromLong(long ival) { register PyIntObject *v; #if NSMALLNEGINTS + NSMALLPOSINTS > 0 //[1] :尝试使用小整数对象池 if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { v = small_ints[ival + NSMALLNEGINTS]; Py_INCREF(v); return (PyObject *) v; } #endif //[2] :为通用整数对象池申请新的内存空间 if (free_list == NULL) { if ((free_list = fill_free_list()) == NULL) return NULL; } //[3] : (inline)内联PyObject_New的行为 v = free_list; free_list = (PyIntObject *)v->ob_type; PyObject_INIT(v, &PyInt_Type); v->ob_ival = ival; return (PyObject *) v; }
fill_free_list
就是创建大整数缓冲池内存空间的逻辑,该函数返回一个free_list
链表,当整数对象ival创建成功后,free_list
表头就指向了v->ob_type
,ob_type
不是所有Python对象中表示类型信息的字段吗?怎么在这里作为一个连接指针呢?这是Python在性能与代码优雅之间取中庸之道,对名称的滥用,放弃了对类型安全的坚持。把它理解成指向下一个PyIntObject的指针即可。
[intobject.c] static PyIntObject* fill_free_list(void) { PyIntObject *p, *q; // 申请大小为sizeof(PyIntBlock)的内存空间 // block list始终指向最新创建的PyIntBlock p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock)); ((PyIntBlock *)p)->next = block_list; block_list = (PyIntBlock *)p; //:将PyIntBlock中的PyIntObject数组(objects)转变成单向链表 p = &((PyIntBlock *)p)->objects[0]; q = p + N_INTOBJECTS; while (--q > p) // ob_type指向下一个未被使用的PyIntObject。 q->ob_type = (struct _typeobject *)(q-1); q->ob_type = NULL; return p + N_INTOBJECTS - 1; }
不同的PyIntBlock里面的空闲的内存是怎样连接起来构成free_list
的呢?这个秘密放在了整数对象垃圾回收的时候,在PyIntObject对象的tp_dealloc操作中可以看到:
[intobject.c] static void int_dealloc(PyIntObject *v) { if (PyInt_CheckExact(v)) { v->ob_type = (struct _typeobject *)free_list; free_list = v; } else v->ob_type->tp_free((PyObject *)v); }
原来PyIntObject对象销毁时,它所占用的内存并不会释放,而是继续被Python使用,进而将free_list
表头指向了这个要被销毁的对象上。
总结
- Python中的int对象就是c语言中long类型数值的扩展
- 小整数对象[-5, 257]在python中是共享的
- 整数对象都是从缓冲池中获取的。
- 整数对象回收时,内存并不会归还给系统,而是将其对象的ob_type指向free_list,供新创建的整数对象使用
源码参考:
- intobject.c
- Python字符串实现原理
- Python列表对象实现原理
- Python字典对象实现原理
在Python世界中将对象分为两种:一种是定长对象,比如整数,整数对象定义的时候就能确定它所占用的内存空间大小,另一种是变长对象,在对象定义时并不知道是多少,比如:str,list, set, dict等。
>>> import sys >>> sys.getsizeof(1000) 28 >>> sys.getsizeof(2000) 28 >>> sys.getsizeof("python") 55 >>> sys.getsizeof("java") 53
如上,整数对象所占用的内存都是28字节,和具体的值没关系,而同样都是字符串对象,不同字符串对象所占用的内存是不一样的,这就是变长对象,对于变长对象,在对象定义时是不知道对象所占用的内存空间是多少的。
字符串对象在Python内部用PyStringObject表示,PyStringObject和PyIntObject一样都属于不可变对象,对象一旦创建就不能改变其值。(注意:变长对象和不可变对象是两个不同的概念)。PythonStringObject的定义:
[stringobject.h] typedef struct { PyObject_VAR_HEAD long ob_shash; int ob_sstate; char ob_sval[1]; } PyStringObject;
不难看出Python的字符串对象内部就是由一个字符数组维护的,在整数的实现原理一文中提到PyObject_HEAD
,对于PyObject_VAR_HEAD
就是在PyObject_HEAD
基础上多出一个ob_size
属性:
[object.h] #define PyObject_VAR_HEAD PyObject_HEAD int ob_size; /* Number of items in variable part */ typedef struct { PyObject_VAR_HEAD } PyVarObject;
ob_size
保存了变长对象中元素的长度,比如PyStringObject对象"Python"的ob_size
为6。ob_sval
是一个初始大小为1的字符数组,且ob_sval[0] = " ",但实际上创建一个PyStringObject时ob_sval
指向的是一段长为ob_size
+1个字节的内存。ob_shash
是字符串对象的哈希值,初始值为-1,在第一次计算出字符串的哈希值后,会把该值缓存下来,赋值给ob_shash
。ob_sstate
用于标记该字符串对象是否进过intern机制处理(后文会介绍)。
PYSTRINGOBJECT对象创建过程
[stringobject.c] PyObject * PyString_FromString(const char *str) { register size_t size; register PyStringObject *op; assert(str != NULL); size = strlen(str); // [1] if (size > PY_SSIZE_T_MAX - PyStringObject_SIZE) { PyErr_SetString(PyExc_OverflowError, "string is too long for a Python string"); return NULL; } // [2] if (size == 0 && (op = nullstring) != NULL) { #ifdef COUNT_ALLOCS null_strings++; #endif Py_INCREF(op); return (PyObject *)op; } // [3] if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) { #ifdef COUNT_ALLOCS one_strings++; #endif Py_INCREF(op); return (PyObject *)op; } // [4] /* Inline PyObject_NewVar */ op = (PyStringObject *)PyObject_MALLOC(PyStringObject_SIZE + size); if (op == NULL) return PyErr_NoMemory(); PyObject_INIT_VAR(op, &PyString_Type, size); op->ob_shash = -1; op->ob_sstate = SSTATE_NOT_INTERNED; Py_MEMCPY(op->ob_sval, str, size+1); /* share short strings */ if (size == 0) { PyObject *t = (PyObject *)op; PyString_InternInPlace(&t); op = (PyStringObject *)t; nullstring = op; Py_INCREF(op); } else if (size == 1) { PyObject *t = (PyObject *)op; PyString_InternInPlace(&t); op = (PyStringObject *)t; characters[*str & UCHAR_MAX] = op; Py_INCREF(op); } return (PyObject *) op; }
- 如果字符串的长度超出了Python所能接受的最大长度(32位平台是2G),则返回Null。
- 如果是空字符串,那么返回特殊的PyStringObject,即nullstring。
- 如果字符串的长度为1,那么返回特殊PyStringObject,即onestring。
- 其他情况下就是分配内存,初始化PyStringObject,把参数str的字符数组拷贝到PyStringObject中的
ob_sval
指向的内存空间。
字符串的INTERN机制
PyStringObject的ob_sstate
属性用于标记字符串对象是否经过intern机制处理,intern处理后的字符串,比如"Python",在解释器运行过程中始终只有唯一的一个字符串"Python"对应的PyStringObject对象。
>>> a = "python" >>> b = "python" >>> a is b True
如上所示,创建a时,系统首先会创建一个新的PyStringObject对象出来,然后经过intern机制处理(PyString_InternInPlace),接着查找经过intern机制处理的PyStringObject对象,如果发现有该字符串对应的PyStringObject存在,则直接返回该对象,否则把刚刚创建的PyStringObject加入到intern机制中。由于a和b字符串字面值是一样的,因此a和b都指向同一个PyStringObject("python")对象。那么intern内部又是一个什么样的机制呢?
[stringobject.c] static PyObject *interned; void PyString_InternInPlace(PyObject **p) { register PyStringObject *s = (PyStringObject *)(*p); PyObject *t; if (s == NULL || !PyString_Check(s)) Py_FatalError("PyString_InternInPlace: strings only please!"); /* If it"s a string subclass, we don"t really know what putting it in the interned dict might do. */ // [1] if (!PyString_CheckExact(s)) return; // [2] if (PyString_CHECK_INTERNED(s)) return; // [3] if (interned == NULL) { interned = PyDict_New(); if (interned == NULL) { PyErr_Clear(); /* Don"t leave an exception */ return; } } t = PyDict_GetItem(interned, (PyObject *)s); if (t) { Py_INCREF(t); Py_DECREF(*p); *p = t; return; } if (PyDict_SetItem(interned, (PyObject *)s, (PyObject *)s) < 0) { PyErr_Clear(); return; } /* The two references in interned are not counted by refcnt. The string deallocator will take care of this */ Py_REFCNT(s) -= 2; PyString_CHECK_INTERNED(s) = SSTATE_INTERNED_MORTAL; }
- 先类型检查,intern机制只处理字符串
- 如果该PyStringObject对象已经进行过intern机制处理,则直接返回
- interned其实一个字典对象,当它为null时,初始化一个字典对象,否则,看该字典中是否存在一个key为
(PyObject *)s
的value,如果存在,那么就把该对象的引用计数加1,临时创建的那个对象的引用计数减1。否则,把(PyObject *)s
同时作为key和value添加到interned字典中,与此同时它的引用计数减2,这两个引用计数减2是因为被interned字典所引用,但这两个引用不作为垃圾回收的判断依据,否则,字符串对象永远都不会被垃圾回收器收集了。
上述代码中,给b赋值为"python"后,系统中创建了几个PyStringObject对象呢?答案是:2,在创建b的时候,一定会有一个临时的PyStringObject作为字典的key在interned中查找是否存在一个PyStringObject对象的值为"python"。
字符串的缓冲池
字符串除了有intern机制缓存字符串之外,字符串还有一种专门的短字符串缓冲池characters
。用于缓存字符串长度为1的PyStringObject对象。
static PyStringObject *characters[UCHAR_MAX + 1]; //UCHAR_MAX = 255
创建长度为1的字符串时流程:
... else if (size == 1) { PyObject *t = (PyObject *)op; PyString_InternInPlace(&t); op = (PyStringObject *)t; characters[*str & UCHAR_MAX] = op; Py_INCREF(op);
- 首先创建一个PyStringObject对象。
- 进行intern操作
- 将PyStringObject缓存到characters中
- 引用计数增1
总结:
1. 字符串用PyStringObject表示 2. 字符串属于变长对象 3. 字符串属于不可变对象 4. 字符串用intern机制提高python的效率 5. 字符串有专门的缓冲池存储长度为1的字符串对象
参考:
stringobject.c Python整数对象实现原理 Python列表对象实现原理 Python字典对象实现原理
Python中的列表基于PyListObject实现,列表支持元素的插入、删除、更新操作,因此PyListObject是一个变长对象(列表的长度随着元素的增加和删除而变长和变短),同时它还是一个可变对象(列表中的元素根据列表的操作而发生变化,内存大小动态的变化),PyListObject的定义:
typedef struct { # 列表对象引用计数 int ob_refcnt; # 列表类型对象 struct _typeobject *ob_type; # 列表元素的长度 int ob_size; /* Number of items in variable part */ # 真正存放列表元素容器的指针,list[0] 就是 ob_item[0] PyObject **ob_item; # 当前列表可容纳的元素大小 Py_ssize_t allocated; } PyListObject;
咋一看PyListObject对象的定义非常简单,除了通用对象都有的引用计数(ob_refcnt)、类型信息(ob_type),以及变长对象的长度(ob_size)之外,剩下的只有ob_item,和allocated,ob_item是真正存放列表元素容器的指针,专门有一块内存用来存储列表元素,这块内存的大小就是allocated所能容纳的空间。alloocated是列表所能容纳的元素大小,而且满足条件:
- 0 <= ob_size <= allocated
- len(list) == ob_size
- ob_item == NULL 时 ob_size == allocated == 0
列表对象的创建
PylistObject对象的是通过函数PyList_New创建而成,接收参数size,该参数用于指定列表对象所能容纳的最大元素个数。
// 列表缓冲池, PyList_MAXFREELIST为80 static PyListObject *free_list[PyList_MAXFREELIST]; //缓冲池当前大小 static int numfree = 0; PyObject *PyList_New(Py_ssize_t size) { PyListObject *op; //列表对象 size_t nbytes; //创建列表对象需要分配的内存大小 if (size < 0) { PyErr_BadInternalCall(); return NULL; } /* Check for overflow without an actual overflow, * which can cause compiler to optimise out */ if ((size_t)size > PY_SIZE_MAX / sizeof(PyObject *)) return PyErr_NoMemory(); nbytes = size * sizeof(PyObject *); if (numfree) { numfree--; op = free_list[numfree]; _Py_NewReference((PyObject *)op); } else { op = PyObject_GC_New(PyListObject, &PyList_Type); if (op == NULL) return NULL; } if (size <= 0) op->ob_item = NULL; else { op->ob_item = (PyObject **) PyMem_MALLOC(nbytes); if (op->ob_item == NULL) { Py_DECREF(op); return PyErr_NoMemory(); } memset(op->ob_item, 0, nbytes); } # 设置ob_size Py_SIZE(op) = size; op->allocated = size; _PyObject_GC_TRACK(op); return (PyObject *) op; }
创建过程大致是:
- 检查size参数是否有效,如果小于0,直接返回NULL,创建失败
- 检查size参数是否超出Python所能接受的大小,如果大于PY_SIZE_MAX(64位机器为8字节,在32位机器为4字节),内存溢出。
- 检查缓冲池free_list是否有可用的对象,有则直接从缓冲池中使用,没有则创建新的PyListObject,分配内存。
- 初始化ob_item中的元素的值为Null
- 设置PyListObject的allocated和ob_size。
PYLISTOBJECT对象的缓冲池
free_list是PyListObject对象的缓冲池,其大小为80,那么PyListObject对象是什么时候加入到缓冲池free_list的呢?答案在list_dealloc方法中:
static void list_dealloc(PyListObject *op) { Py_ssize_t i; PyObject_GC_UnTrack(op); Py_TRASHCAN_SAFE_BEGIN(op) if ( i = Py_SIZE(op); while (--i >= 0) { Py_XDECREF(op->ob_item[i]); } PyMem_FREE(op->ob_item); } if (numfree < PyList_MAXFREELIST && PyList_CheckExact(op)) free_list[numfree++] = op; else Py_TYPE(op)->tp_free((PyObject *)op); Py_TRASHCAN_SAFE_END(op) }
当PyListObject对象被销毁的时候,首先将列表中所有元素的引用计数减一,然后释放ob_item占用的内存,只要缓冲池空间还没满,那么就把该PyListObject加入到缓冲池中(此时PyListObject占用的内存并不会正真正回收给系统,下次创建PyListObject优先从缓冲池中获取PyListObject),否则释放PyListObject对象的内存空间。
列表元素插入
设置列表某个位置的值时,如“list[1]=0”,列表的内存结构并不会发生变化,而往列表中插入元素时会改变列表的内存结构:
static int ins1(PyListObject *self, Py_ssize_t where, PyObject *v) { // n是列表元素长度 Py_ssize_t i, n = Py_SIZE(self); PyObject **items; if (v == NULL) { PyErr_BadInternalCall(); return -1; } if (n == PY_SSIZE_T_MAX) { PyErr_SetString(PyExc_OverflowError, "cannot add more objects to list"); return -1; } if (list_resize(self, n+1) == -1) return -1; if (where < 0) { where += n; if (where < 0) where = 0; } if (where > n) where = n; items = self->ob_item; for (i = n; --i >= where; ) items[i+1] = items[i]; Py_INCREF(v); items[where] = v; return 0; }
相比设置某个列表位置的值来说,插入操作要多一次PyListObject容量大小的调整,逻辑是list_resize,其次是挪动where之后的元素位置。
// newsize: 列表新的长度 static int list_resize(PyListObject *self, Py_ssize_t newsize) { PyObject **items; size_t new_allocated; Py_ssize_t allocated = self->allocated; if (allocated >= newsize && newsize >= (allocated >> 1)) { assert(self->ob_item != NULL || newsize == 0); Py_SIZE(self) = newsize; return 0; } new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6); /* check for integer overflow */ if (new_allocated > PY_SIZE_MAX - newsize) { PyErr_NoMemory(); return -1; } else { new_allocated += newsize; } if (newsize == 0) new_allocated = 0; items = self->ob_item; if (new_allocated <= (PY_SIZE_MAX / sizeof(PyObject *))) PyMem_RESIZE(items, PyObject *, new_allocated); else items = NULL; if (items == NULL) { PyErr_NoMemory(); return -1; } self->ob_item = items; Py_SIZE(self) = newsize; self->allocated = new_allocated; return 0; }
满足 allocated >= newsize && newsize >= (allocated /2)
时,简单改变list的元素长度,PyListObject对象不会重新分配内存空间,否则重新分配内存空间,如果newsize<allocated/2
,那么会减缩内存空间,如果newsize>allocated
,就会扩大内存空间。当newsize==0
时内存空间将缩减为0。
总结
- PyListObject缓冲池的创建发生在列表销毁的时候。
- PyListObject对象的创建分两步:先创建PyListObject对象,然后初始化元素列表为NULL。
- PyListObject对象的销毁分两步:先销毁PyListObject对象中的元素列表,然后销毁PyListObject本身。
- PyListObject对象内存的占用空间会根据列表长度的变化而调整。
参考:
- listobject.h
- listobject.c
字典类型是Python中最常用的数据类型之一,它是一个键值对的集合,字典通过键来索引,关联到相对的值,理论上它的查询复杂度是 O(1) :
>>> d = {"a": 1, "b": 2} >>> d["c"] = 3 >>> d {"a": 1, "b": 2, "c": 3}
在字符串的实现原理文章中,曾经出现过字典对象用于intern操作,那么字典的内部结构是怎样的呢?PyDictObject对象就是dict的内部实现。
哈希表 (HASH TABLES)
哈希表(也叫散列表),根据关键值对(Key-value)而直接进行访问的数据结构。它通过把key和value映射到表中一个位置来访问记录,这种查询速度非常快,更新也快。而这个映射函数叫做哈希函数,存放值的数组叫做哈希表。 哈希函数的实现方式决定了哈希表的搜索效率。具体操作过程是:
- 数据添加:把key通过哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。
- 数据查询:再次使用哈希函数将key转换为对应的数组下标,并定位到数组的位置获取value。
但是,对key进行hash的时候,不同的key可能hash出来的结果是一样的,尤其是数据量增多的时候,这个问题叫做哈希冲突。如果解决这种冲突情况呢?通常的做法有两种,一种是链接法,另一种是开放寻址法,Python选择后者。
开放寻址法(OPEN ADDRESSING)
开放寻址法中,所有的元素都存放在散列表里,当产生哈希冲突时,通过一个探测函数计算出下一个候选位置,如果下一个获选位置还是有冲突,那么不断通过探测函数往下找,直到找个一个空槽来存放待插入元素。
PYDICTENTRY
字典中的一个key-value键值对元素称为entry(也叫做slots),对应到Python内部是PyDictEntry,PyDictObject就是PyDictEntry的集合。PyDictEntry的定义是:
typedef struct { /* Cached hash code of me_key. Note that hash codes are C longs. * We have to use Py_ssize_t instead because dict_popitem() abuses * me_hash to hold a search finger. */ Py_ssize_t me_hash; PyObject *me_key; PyObject *me_value; } PyDictEntry;
me_hash用于缓存me_key的哈希值,防止每次查询时都要计算哈希值,entry有三种状态。
-
Unused: me_key == me_value == NULL
Unused是entry的初始状态,key和value都为NULL。插入元素时,Unused状态转换成Active状态。这是me_key为NULL的唯一情况。 2. Active: me_key != NULL and me_key != dummy 且 me_value != NULL
插入元素后,entry就成了Active状态,这是me_value唯一不为NULL的情况,删除元素时Active状态刻转换成Dummy状态。 3. Dummy: me_key == dummy 且 me_value == NULL
此处的dummy对象实际上一个PyStringObject对象,仅作为指示标志。Dummy状态的元素可以在插入元素的时候将它变成Active状态,但它不可能再变成Unused状态。
为什么entry有Dummy状态呢?这是因为采用开放寻址法中,遇到哈希冲突时会找到下一个合适的位置,例如某元素经过哈希计算应该插入到A处,但是此时A处有元素的,通过探测函数计算得到下一个位置B,仍然有元素,直到找到位置C为止,此时ABC构成了探测链,查找元素时如果hash值相同,那么也是顺着这条探测链不断往后找,当删除探测链中的某个元素时,比如B,如果直接把B从哈希表中移除,即变成Unused状态,那么C就不可能再找到了,因为AC之间出现了断裂的现象,正是如此才出现了第三种状态---Dummy,Dummy是一种类似的伪删除方式,保证探测链的连续性。
PYDICTOBJECT
PyDictObject就是PyDictEntry对象的集合,PyDictObject的结构是:
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]; };
- ma_fill :所有处于Active以及Dummy的元素个数
- ma_used :所有处于Active状态的元素个数
- ma_mask :所有entry的元素个数(Active+Dummy+Unused)
- ma_smalltable:创建字典对象时,一定会创建一个大小为PyDict_MINSIZE==8的PyDictEntry数组。
- ma_table:当entry数量小于PyDict_MINSIZE,ma_table指向ma_smalltable的首地址,当entry数量大于8时,Python把它当做一个大字典来处理,此刻会申请额外的内存空间,同时将ma_table指向这块空间。
- ma_lookup:字典元素的搜索策略
PyDictObject使用PyObject_HEAD而不是PyObject_Var_HEAD,虽然字典也是变长对象,但此处并不是通过ob_size来存储字典中元素的长度,而是通过ma_used字段。
PYDICTOBJECT的创建过程
PyObject * PyDict_New(void) { register PyDictObject *mp; if (dummy == NULL) { /* Auto-initialize dummy */ dummy = PyString_FromString("<dummy key>"); if (dummy == NULL) return NULL; } if (numfree) { mp = free_list[--numfree]; assert (mp != NULL); assert (Py_TYPE(mp) == &PyDict_Type); _Py_NewReference((PyObject *)mp); if (mp->ma_fill) { EMPTY_TO_MINSIZE(mp); } else { /* At least set ma_table and ma_mask; these are wrong if an empty but presized dict is added to freelist */ INIT_NONZERO_DICT_SLOTS(mp); } assert (mp->ma_used == 0); assert (mp->ma_table == mp->ma_smalltable); assert (mp->ma_mask == PyDict_MINSIZE - 1); } else { mp = PyObject_GC_New(PyDictObject, &PyDict_Type); if (mp == NULL) return NULL; EMPTY_TO_MINSIZE(mp); } mp->ma_lookup = lookdict_string; return (PyObject *)mp; }
- 初始化dummy对象
- 如果缓冲池还有可用的对象,则从缓冲池中读取,否则,执行步骤3
- 分配内存空间,创建PyDictObject对象,初始化对象
- 指定添加字典元素时的探测函数,元素的搜索策略
字典搜索策略
static PyDictEntry * lookdict(PyDictObject *mp, PyObject *key, register long hash) { register size_t i; register size_t perturb; register PyDictEntry *freeslot; register size_t mask = (size_t)mp->ma_mask; PyDictEntry *ep0 = mp->ma_table; register PyDictEntry *ep; register int cmp; PyObject *startkey; i = (size_t)hash & mask; ep = &ep0[i]; if (ep->me_key == NULL || ep->me_key == key) return ep; if (ep->me_key == dummy) freeslot = ep; else { if (ep->me_hash == hash) { startkey = ep->me_key; Py_INCREF(startkey); cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); Py_DECREF(startkey); if (cmp < 0) return NULL; if (ep0 == mp->ma_table && ep->me_key == startkey) { if (cmp > 0) return ep; } else { /* The compare did major nasty stuff to the * dict: start over. * XXX A clever adversary could prevent this * XXX from terminating. */ return lookdict(mp, key, hash); } } freeslot = NULL; } /* In the loop, me_key == dummy is by far (factor of 100s) the least likely outcome, so test for that last. */ for (perturb = hash; ; perturb >>= PERTURB_SHIFT) { i = (i << 2) + i + perturb + 1; ep = &ep0[i & mask]; if (ep->me_key == NULL) return freeslot == NULL ? ep : freeslot; if (ep->me_key == key) return ep; if (ep->me_hash == hash && ep->me_key != dummy) { startkey = ep->me_key; Py_INCREF(startkey); cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); Py_DECREF(startkey); if (cmp < 0) return NULL; if (ep0 == mp->ma_table && ep->me_key == startkey) { if (cmp > 0) return ep; } else { /* The compare did major nasty stuff to the * dict: start over. * XXX A clever adversary could prevent this * XXX from terminating. */ return lookdict(mp, key, hash); } } else if (ep->me_key == dummy && freeslot == NULL) freeslot = ep; } assert(0); /* NOT REACHED */ return 0; }
字典在添加元素和查询元素时,都需要用到字典的搜索策略,搜索时,如果不存在该key,那么返回Unused状态的entry,如果存在该key,但是key是一个Dummy对象,那么返回Dummy状态的entry,其他情况就表示存在Active状态的entry,那么对于字典的插入操作,针对不同的情况进行操作也不一样。对于Active的entry,直接替换me_value值即可;对于Unused或Dummy的entry,需要同时设置me_key,me_hash和me_value
PYDICTOBJECT对象缓冲池
PyDictObject对象缓冲池和PyListObject对象缓冲池的原理是类似的,都是在对象被销毁的时候把该对象添加到缓冲池中去,而且值保留PyDictObject对象本身,如果ma_table维护的时从系统堆中申请的空间,那么Python会释放这块内存,如果ma_table维护的是ma_smalltable,那么只需把smalltable中的元素的引用计数减少即可。
static void dict_dealloc(register PyDictObject *mp) { register PyDictEntry *ep; Py_ssize_t fill = mp->ma_fill; PyObject_GC_UnTrack(mp); Py_TRASHCAN_SAFE_BEGIN(mp) for (ep = mp->ma_table; fill > 0; ep++) { if (ep->me_key) { --fill; Py_DECREF(ep->me_key); Py_XDECREF(ep->me_value); } } if (mp->ma_table != mp->ma_smalltable) PyMem_DEL(mp->ma_table); if (numfree < PyDict_MAXFREELIST && Py_TYPE(mp) == &PyDict_Type) free_list[numfree++] = mp; else Py_TYPE(mp)->tp_free((PyObject *)mp); Py_TRASHCAN_SAFE_END(mp) }
- 上一篇: 浅谈python字符串存储形式
- 下一篇: Python 类的实 例化