首页 > 代码库 > 《python源码剖析》笔记 python中的Dict对象

《python源码剖析》笔记 python中的Dict对象

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie


1.PyDictObject对象 -->  C++ STL中的map是基于RB-tree的,搜索时间复杂度是O(logN)
PyDictObject采用了hash表,时间复杂度是O(1)

typedef struct{
	Py_ssize_t me_hash; //me_key的hash值,避免每次查询都要重新计算一遍hash值
	PyObject *me_key;
	PyObject *me_value;
}PyDictEntry;

将(key,value)对称为entry,它可以在3种状态间转换:
Unused态 --> me_key和 me_value都为NULL
Active态 --> me_key和 me_value都不为NULL
Dummy态  --> me_key为dummy, me_value为NULL
图5-2
typedef struct _dictobject PyDictObject;
struct _dictobject{
	PyObject_HEAD
	Py_ssize_t ma_fill; //元素个数: Active + Dummy
	Py_ssize_t ma_used; //元素个数: Active
	Py_ssize_t ma_mask; //记录了entry的数量
	
	PyDictEntry *ma_table;
    PyDictEntry *(*ma_lookup)(PyDictObject *mp, PyObject *key, long hash);
    PyDictEntry ma_smalltable[PyDict_MINSIZE];
}

PyDict_MINSIZE默认设定为8
当 PyDictObject对象的entry数量少于8个, ma_table将指向 ma_smalltable
当 PyDictObject对象的entry数量大于8个, ma_table将指向额外申请的内存空间
Q:这个时候 ma_smalltable中的对象怎么办?
A:ma_smalltable里的对象全都复制到新的table里
图5-3

PyDict_Type --> PyDictObject的类型对象
PyTypeObject PyDict_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "dict",
    sizeof(PyDictObject),
    0,
    (destructor)dict_dealloc,                   /* tp_dealloc */
    (printfunc)dict_print,                      /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    (cmpfunc)dict_compare,                      /* tp_compare */
    (reprfunc)dict_repr,                        /* tp_repr */
    0,                                          /* tp_as_number */
    &dict_as_sequence,                          /* tp_as_sequence */
    &dict_as_mapping,                           /* tp_as_mapping */
    (hashfunc)PyObject_HashNotImplemented,      /* tp_hash */
    //...
};

2.PyDictObject的创建
一个途径:
PyDict_New

PyObject* PyDict_New(void) 
{
	register dictobject *mp;
	//[1]:自动创建 dummy对象
	//防止探测序列中断
	if (dummy == NULL) { /* Auto-initialize dummy */
		dummy = PyString_FromString("<dummy key>");
	} 
	if (num_free_dicts) 
	{
		…… //[2]:使用缓冲池 
	} 
	else 
	{
		//[3]:创建 PyDictObject对象
		mp = PyObject_GC_New(dictobject, &PyDict_Type);
		EMPTY_TO_MINSIZE(mp);
	}
	mp->ma_lookup = lookdict_string;
	return (PyObject *)mp; 
}
//EMPTY_TO_MINSIZE --> ma_size, ma_fill = 0
//INIT_NONZERO_DICT_SLOT --> 将 ma_table指向 ma_smalltable

元素搜索
static PyDictEntry *
lookdict(PyDictObject *mp, PyObject *key, register long hash)
{
    register size_t i;
    register size_t perturb;
    register PyDictEntry *freeslot; //freeslot用来指向探测序列中第一个处于Dummy态的entry
    register size_t mask = (size_t)mp->ma_mask;
    PyDictEntry *ep0 = mp->ma_table;
    register PyDictEntry *ep;
    register int cmp;
    PyObject *startkey;
	
	//[1]:hash,定位冲突探测链的第一个entry
    i = (size_t)hash & mask; //将哈希值映射到哈希表大小范围内
    ep = &ep0[i];
	
	//[2]:
	//1. entry处于Unused态
	//2. entry中的 key与待搜索的 key匹配
    if (ep->me_key == NULL || ep->me_key == key) //**引用相同检查
        return ep;


	//[3]:第一个entry处于 Dummy态,设置freeslot
    if (ep->me_key == dummy)
        freeslot = ep;
    else {
		//检查Active态entry
        if (ep->me_hash == hash) {
            startkey = ep->me_key;
            cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);//**值相同检查
            if (cmp < 0)
                return NULL;
        }
        freeslot = NULL;
    }


    for (perturb = hash; ; perturb >>= PERTURB_SHIFT) {
		//[5]:寻找探测链上下一个entry
        i = (i << 2) + i + perturb + 1;//?
        ep = &ep0[i & mask];
		//[6]:到达 Unused态 entry,搜索失败
        if (ep->me_key == NULL)
			//如果 freeslot不为空,返回freeslot所指的entry
			//如果 freeslot为空,返回该 Unused态的 entry
            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;
            cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);
            if (cmp > 0)
				return ep;
            }
            else {   
                return lookdict(mp, key, hash);
            }
        }
        else if (ep->me_key == dummy && freeslot == NULL)
            freeslot = ep;
    }
}
//失败或成功返回entry,失败的话,entry的me_value为NULL

插入与删除
insertdict
1.搜索成功,返回处于Active态的 entry,直接替换 me_value
2.搜索失败,返回 Unused态或 Dummy态的entry,完整设置 me_key,me_hash,me_value
在调用insertdict之前会调用PyDict_SetItem,是由 PyDict_SetItem调用 insertdict的
1.计算hash值
2.插入(key, value)元素对 //在这里调用insertdict
3.必要时调整dict的内存空间 //当搜索失败且装载率大于或等于2/3时就调整dict的内存空间
调用 dictresize改变dict的table大小
1.确定新的table的大小
2.如果新的table大小为8,就可以直接使用ma_smalltable
3.否则,需要在堆上申请空间
4.设置新的table
5.处理旧table中的entry:
  Active态entry,搬移到新的table中
  Dummy态的entry,调整 key的引用计数,丢弃该entry
6.必要时释放旧table所维护的内存空间
PyDict_DelItem
1.获得hash值
2.搜索entry
3.删除 entry所维护的元素,将 entry的状态转为 dummy态

3.PyDictObject对象缓冲池
与PyListObject中使用的缓冲池一样,最初什么都没有,当第一个PyDictObject当销毁时,
这个缓冲池才开始接纳被缓冲的PyDictObject对象
static void
dict_dealloc(register PyDictObject *mp)
{
    register PyDictEntry *ep;
    Py_ssize_t fill = mp->ma_fill;
    //[1]:调整dict中对象的引用计数
    for (ep = mp->ma_table; fill > 0; ep++) {
        if (ep->me_key) {
            --fill;
            Py_DECREF(ep->me_key);
            Py_XDECREF(ep->me_value);
        }
    }
	//[2]:释放从系统堆中申请的内存空间
    if (mp->ma_table != mp->ma_smalltable)
        PyMem_DEL(mp->ma_table);
	//[3]:将被销毁的PyDictObject对象放入缓冲池
    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)
}

在创建新的 PyDictObject对象时,如果在缓冲池中有可以使用的对象,则直接从缓冲池中取出使用,而不需要重新创建