《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

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里

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对象时,假设在缓冲池中有能够使用的对象,则直接从缓冲池中取出使用,而不须要又一次创建

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

时间: 2024-10-20 12:58:49

《python源代码剖析》笔记 python中的Dict对象的相关文章

Python源码剖析笔记3-Python执行原理初探

Python源码剖析笔记3-Python执行原理初探 本文简书地址:http://www.jianshu.com/p/03af86845c95 之前写了几篇源码剖析笔记,然而慢慢觉得没有从一个宏观的角度理解python执行原理的话,从底向上分析未免太容易让人疑惑,不如先从宏观上对python执行原理有了一个基本了解,再慢慢探究细节,这样也许会好很多.这也是最近这么久没有更新了笔记了,一直在看源码剖析书籍和源码,希望能够从一个宏观层面理清python执行原理.人说读书从薄读厚,再从厚读薄方是理解了

Python源码剖析笔记0 ——C语言基础

python源码剖析笔记0--C语言基础回顾 要分析python源码,C语言的基础不能少,特别是指针和结构体等知识.这篇文章先回顾C语言基础,方便后续代码的阅读. 1 关于ELF文件 linux中的C编译得到的目标文件和可执行文件都是ELF格式的,可执行文件中以segment来划分,目标文件中,我们是以section划分.一个segment包含一个或多个section,通过readelf命令可以看到完整的section和segment信息.看一个栗子: char pear[40]; static

python源码剖析笔记1——Python对象初见

python源码剖析笔记1--Python对象初见 工作整两年了,用python最多,然而对于python内部机制不一定都清楚,每天沉醉于增删改查的简单逻辑编写,实在耗神.很多东西不用就忘记了,比如C语言,正好,python源码用C写的,分析python源码的同时又能温故C语言基础,实在是件很好的事情.另外,还有陈儒大神的<python源码剖析>做指引,分析也不至于没头没脑.期望在一个月的业余时间,能有所小成,以此为记. 1 python中的对象 python中,一切东西都是对象,在c语言实现

Python源码剖析笔记4-内建数据类型

Python源码剖析笔记4-内建数据类型 Python内建数据类型包括整数对象PyIntObject,字符串对象PyStringObject,列表对象PyListObject以及字典对象PyDictObject等.整数对象之前已经分析过了,这一篇文章准备分析下余下几个对象,这次在<python源码剖析>中已经写的很详细的部分就不赘述了,主要是总结一些之前看书时疑惑的地方. 1 整数对象-PyIntObject 参见 python整数对象. 2 字符串对象-PyStringObject 2.1

Python源码剖析笔记2-Python整数对象

Python源码剖析笔记2-Python整数对象 本文简书地址: http://www.jianshu.com/p/0136ed90cd46 千里之行始于足下,从简单的类别开始分析,由浅入深也不至于自己丧失信心.先来看看Python整数对象,也就是python中的PyIntObject对象,对应的类型对象是PyInt_Type. 1 Python整数对象概览 为了性能考虑,python中对小整数有专门的缓存池,这样就不需要每次使用小整数对象时去用malloc分配内存以及free释放内存.pyth

Python源码剖析笔记6-函数机制

Python的函数机制是很重要的部分,很多时候用python写脚本,就是几个函数简单解决问题,不需要像java那样必须弄个class什么的. 本文简书地址:http://www.jianshu.com/p/d00108741a18 1 函数对象PyFunctionObject PyFunctionObject对象的定义如下: typedef struct { PyObject_HEAD PyObject *func_code; /* A code object */ PyObject *func

Python源码剖析笔记5-模块机制

本文简书地址: http://www.jianshu.com/p/14586ec50ab6 python中经常用到模块,比如import xxx,from xxx import yyy这样子,里面的机制也是需要好好探究一下的,这次主要从黑盒角度来探测模块机制,源码分析点到为止,详尽的源码分析见陈儒大神的<python源码剖析>第14章. 1 如何导入模块 首先来看一个导入模块的例子.创建一个文件夹demo5,文件夹中有如下几个文件. [email protected] ~/demo5 $ ls

Python 源码剖析(四)【LIST对象】

四.LIST对象 1.PyListObject对象 2.PyListObject的创建与维护 3.PyListObject 对象缓冲池 4.Hack PyListObject 1.PyListObject对象 PyListObject 对象是变长对象,而且还是一个可变对象: [listobject.h] typedef struct { PyObject_VAR_HEAD /* Vector of pointers to list elements. list[0] is ob_item[0],

《python源码剖析》笔记 python虚拟机中的一般表达式

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.字节码指令 LOAD_CONST:从consts表中读取序号为i的元素并压入到运行时栈中 STORE_NAME:改变local名字空间.从符号表names取序号为i的元素作为变量名, 取运行时栈的栈顶元素作为变量值,完成从变量名到变量值的映射关系的创建. BUILD_MAP:创建一个空的PyDictObject对象,并压入运行时栈 DUP_TOP:将栈顶元素的引用计数增加1,并将它再次

《python源码剖析》笔记 python虚拟机中的函数机制

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.Python虚拟机在执行函数调用时会动态地创建新的 PyFrameObject对象, 这些PyFrameObject对象之间会形成PyFrameObject对象链,模拟x86平台上运行时栈 2.PyFuctionObject对象 typedef struct { PyObject_HEAD PyObject *func_code: //对应函数编译后的PyCodeObject对象 Py