python字典

Dictionary object implementation using a hash table ,通过描述可知,python的字典就是实现了一个hash表。

Python字典概述

在python的字典中,一个键值对的对应保存就是PyDictEntry类型来保存;

typedef struct {/* Cached hash code of me_key. */Py_hash_t me_hash;PyObject *me_key;PyObject *me_value; /* This field is only meaningful for combined tables */
} PyDictKeyEntry; 

其中,me_hash就是哈希生成的值,me_key就是对应的key值,me_value就是对应的值。
在python中,在一个PyDictObject对象的变化过程中,entry的状态会在不同的状态间转换。基本上在如下四种状态中转换:Unused、Active、Dummy和Pending。

  1. Unused:没有插入任何一个获取的key与value,并且在次之前也没有存储任何的key,value,每一个entry在初始化的时候都会处于这种状态,并且Unused会被里面切换到Active态,当有key插入,这是就是entry初始化的状态。
  2. Active:当index>=0时,me_key不为空并且me_value不为空,保存了一个键值对,Active可以转变为Dummy或者Pending状态,当一个健被删除的时候,这只会在me_value不为空的时候出现。
  3. Dummy:先前保存了一个Active的键值对,但是这个键值对被删除了并且一个活跃的键值对还没有填入该位置,Dummy可以转变为Active当删除的时候,Dummy的位置不能被重新使用,一旦发生碰撞,探针序列就无法知道这对键值对曾是活跃的键值对。
  4. Pending:索引>=0,键!=空,值=空(仅拆分),尚未插入到拆分表中。
字典的两种类型

python的字典类型中包含了两种联合字典(split-table dictionaries)与分离字典(combined-table dictonaries)。详细的信息可查看有关dict的描述pep-0412。

split-table dictionaries

当被创建的字典是用来保存object的__dict__属性时,该字典才会创建为一个split-table,它们的健表都被缓存在类型属性中,并且允许所有该类型的实例都可以共享该keys。当出现一个事件讲字典的属性值进行改变的时候,个别字典讲慢慢的转化成组合表的形式。这就保证了在大部分的应用场景下很高的内存利用效率,并保证了在各个场景下的正确性。当split-dict重新改变大小,它会立马改变为一个combined-table,如果重置大小作为保存实例属性的结果,并且只有一个该object的实例,字典会立马再变为一个split-table。如果从split-table中删除一个key, value,它不会删除keys tables中对应的该值,而只是将values数值中移除了该value。

combined-table dictionaries

直接通过dict內建函数与{}生成的字典,模块和大部分其他字典都会创建为combined-table字典,一个combined-table不会改变为一个split-table字典,该字典的行为方式与最初的字典的行为方式大致相同。

容器的相关数据结构

字典对象是通过PyDictObject来实现数据的,详情如下;

typedef struct _dictkeysobject PyDictKeysObject;/* The ma_values pointer is NULL for a combined table* or points to an array of PyObject* for a split table*/
typedef struct {PyObject_HEAD/* Number of items in the dictionary */Py_ssize_t ma_used;                 // 使用的keys个数/* Dictionary version: globally unique, value change each timethe dictionary is modified */uint64_t ma_version_tag;PyDictKeysObject *ma_keys;     // 如果有则是保存的keys数据/* If ma_values is NULL, the table is "combined": keys and valuesare stored in ma_keys.If ma_values is not NULL, the table is splitted:keys are stored in ma_keys and values are stored in ma_values */PyObject **ma_values;            // 如果不为空则保存的是values
} PyDictObject;

其中,PyDictKeysObject的定义如下;

/* See dictobject.c for actual layout of DictKeysObject */
struct _dictkeysobject {Py_ssize_t dk_refcnt;                  // 引用计数/* Size of the hash table (dk_indices). It must be a power of 2. */Py_ssize_t dk_size;                   // hash table 的大小必须是2的倍数/* Function to lookup in the hash table (dk_indices):- lookdict(): general-purpose, and may return DKIX_ERROR if (andonly if) a comparison raises an exception.- lookdict_unicode(): specialized to Unicode string keys, comparison ofwhich can never raise an exception; that function can never returnDKIX_ERROR.- lookdict_unicode_nodummy(): similar to lookdict_unicode() but furtherspecialized for Unicode string keys that cannot be the <dummy> value.- lookdict_split(): Version of lookdict() for split tables. */dict_lookup_func dk_lookup;                       // 哈希查找函数/* Number of usable entries in dk_entries. */Py_ssize_t dk_usable;                             // 可用的entry数量/* Number of used entries in dk_entries. */ Py_ssize_t dk_nentries;                     // 已经使用的entry数量/* Actual hash table of dk_size entries. It holds indices in dk_entries,or DKIX_EMPTY(-1) or DKIX_DUMMY(-2).Indices must be: 0 <= indice < USABLE_FRACTION(dk_size).The size in bytes of an indice depends on dk_size:- 1 byte if dk_size <= 0xff (char*)- 2 bytes if dk_size <= 0xffff (int16_t*)- 4 bytes if dk_size <= 0xffffffff (int32_t*)- 8 bytes otherwise (int64_t*)Dynamically sized, SIZEOF_VOID_P is minimum. */char dk_indices[];  /* char is required to avoid strict aliasing. */   // 存入的entries/* "PyDictKeyEntry dk_entries[dk_usable];" array follows:see the DK_ENTRIES() macro */
};

相关数据结构的内存布局为;

Python字典示例

本次示例脚本如下:

d = {}
d['1']='2'
d['1']='e'
d.pop('1')

通过Python的反汇编工具获取字节码;

python -m dis dict_test.py

输出的字节码如下;

  2           0 BUILD_MAP                02 STORE_NAME               0 (d)3           4 LOAD_CONST               0 ('2')6 LOAD_NAME                0 (d)8 LOAD_CONST               1 ('1')10 STORE_SUBSCR4          12 LOAD_CONST               2 ('e')14 LOAD_NAME                0 (d)16 LOAD_CONST               1 ('1')18 STORE_SUBSCR5          20 LOAD_NAME                0 (d)22 LOAD_METHOD              1 (pop)24 LOAD_CONST               1 ('1')26 CALL_METHOD              128 POP_TOP30 LOAD_CONST               3 (None)32 RETURN_VALUE

通过字节码指令可知,首先调用了BUILD_MAP来创建一个新的字典,接着就对新建的字典d进行了赋值操作与更新操作,最后调用了pop方法删除一个key。接下来就详细分析一下相关流程。

字典的初始化流程

通过查找BUILD_MAP的虚拟机执行函数;

        TARGET(BUILD_MAP) {Py_ssize_t i;PyObject *map = _PyDict_NewPresized((Py_ssize_t)oparg);    // 新建并初始化一个字典if (map == NULL)goto error;                                           // 如果新建失败则报错for (i = oparg; i > 0; i--) {                            // 检查在新建的过程中是否通过参数传值int err;PyObject *key = PEEK(2*i);PyObject *value = PEEK(2*i - 1);err = PyDict_SetItem(map, key, value);             // 找到对应的值并讲该值设置到map中if (err != 0) {                        // 检查是否报错Py_DECREF(map);goto error;                        // 如果错误就报错处理}}while (oparg--) {Py_DECREF(POP());                       // 弹出栈上输入参数的引用Py_DECREF(POP());}PUSH(map);                              // 讲生成的map压栈DISPATCH();                             // 检查是否需要执行下一条字节码指令}

从该函数的执行可知,初始化的函数是从_PyDict_NewPresized开始,该函数就是生成并初始化一个字典;

PyObject *
_PyDict_NewPresized(Py_ssize_t minused)
{const Py_ssize_t max_presize = 128 * 1024;          // 字典最大的容量Py_ssize_t newsize;PyDictKeysObject *new_keys;                         /* There are no strict guarantee that returned dict can contain minused* items without resize.  So we create medium size dict instead of very* large dict or MemoryError.*/if (minused > USABLE_FRACTION(max_presize)) {       // 检查传入的数量是否超过最大值newsize = max_presize;}else {Py_ssize_t minsize = ESTIMATE_SIZE(minused);    // 获取最小的值,在新建一个空的字典的时候该值为0newsize = PyDict_MINSIZE;                       // 设置字典的最小值 为8while (newsize < minsize) {                     // 如果传入的值大于最小值则调整newsize 大小newsize <<= 1;}}assert(IS_POWER_OF_2(newsize));new_keys = new_keys_object(newsize);                // 生成并初始化一个PyDictKeysObject对象if (new_keys == NULL)return NULL;return new_dict(new_keys, NULL);                    // 生成一个新的对象并返回
}

首先,先计算出需要生成的字典的大小,然后再初始化一个PyDictKeysObject,最后就生成一个PyDictObject返回。继续查看new_keys_object的执行流程;

static PyDictKeysObject *new_keys_object(Py_ssize_t size)
{PyDictKeysObject *dk;Py_ssize_t es, usable;assert(size >= PyDict_MINSIZE);           // 检查size是否大于最小sizeassert(IS_POWER_OF_2(size));              // 检查是否是2的倍数usable = USABLE_FRACTION(size);           // 检查是否可用  根据经验在1/2和2/3之间效果最好if (size <= 0xff) {es = 1;}else if (size <= 0xffff) {es = 2;}
#if SIZEOF_VOID_P > 4else if (size <= 0xffffffff) {es = 4;}
#endifelse {es = sizeof(Py_ssize_t);}if (size == PyDict_MINSIZE && numfreekeys > 0) {      // 是否有缓存,如果有缓存就选择缓存中的dkdk = keys_free_list[--numfreekeys];}else {dk = PyObject_MALLOC(sizeof(PyDictKeysObject)+ es * size+ sizeof(PyDictKeyEntry) * usable);    // 没有缓存可使用的字典则申请内存生成一个if (dk == NULL) {PyErr_NoMemory();return NULL;}}DK_DEBUG_INCREF dk->dk_refcnt = 1;                              // 设置引用计数dk->dk_size = size;                                             // 设置大小dk->dk_usable = usable;                                        // 设置是否可用dk->dk_lookup = lookdict_unicode_nodummy;                       // 设置查找函数dk->dk_nentries = 0;memset(&dk->dk_indices[0], 0xff, es * size);                    // 将申请的内存置空memset(DK_ENTRIES(dk), 0, sizeof(PyDictKeyEntry) * usable);     return dk;
}

主要就是通过传入的size,检查是否超过设置的大小,检查是否有缓存的字典数据可用,如果没有则申请内存重新生成一个dk,最后进行申请到的内存讲内容清空。接着就会进行new_dict初始化数据;

/* Consumes a reference to the keys object */
static PyObject *
new_dict(PyDictKeysObject *keys, PyObject **values)
{PyDictObject *mp;assert(keys != NULL);if (numfree) {                            // 判断缓冲池是否有mp = free_list[--numfree];assert (mp != NULL);assert (Py_TYPE(mp) == &PyDict_Type); _Py_NewReference((PyObject *)mp);              // 使用缓冲池对象    }else {mp = PyObject_GC_New(PyDictObject, &PyDict_Type);    // 缓冲池没有则申请新的对象并初始化if (mp == NULL) {DK_DECREF(keys);free_values(values);return NULL;}}mp->ma_keys = keys;mp->ma_values = values;mp->ma_used = 0;                           // 设置ma_used为0mp->ma_version_tag = DICT_NEXT_VERSION();assert(_PyDict_CheckConsistency(mp));return (PyObject *)mp;
}

new_dict就是根据keys,values设置到从缓冲池或者新生成一个dict对象,最后返回。至此,dict的创建工作已经完成。

字典的插入与查找

通过字节码的指令STORE_SUBSCR可知,该命令就是讲’1’作为key, '2’作为value插入到d中,此时查看该执行函数;

        TARGET(STORE_SUBSCR) {PyObject *sub = TOP();                 // 第一个值为keyPyObject *container = SECOND();        // 该为字典对象 PyObject *v = THIRD();                 // 该为valueint err;STACKADJ(-3);/* container[sub] = v */err = PyObject_SetItem(container, sub, v);  // 调用该方法设置值Py_DECREF(v);Py_DECREF(container);Py_DECREF(sub);if (err != 0)goto error;DISPATCH();

此时,从栈中取出相关参数,并将这些值传入PyObject_SetItem函数进行处理设置值;

int
PyObject_SetItem(PyObject *o, PyObject *key, PyObject *value)
{PyMappingMethods *m;if (o == NULL || key == NULL || value == NULL) {           // 检查是否为空如果任一为空则报错null_error();return -1;}m = o->ob_type->tp_as_mapping;                      // 获取类型的tp_as_mapping方法集                 if (m && m->mp_ass_subscript)                       // 如果有设置该类型return m->mp_ass_subscript(o, key, value);                    // 调用该mp_ass_subscript方法if (o->ob_type->tp_as_sequence) {                                 // 获取作为队列的操作集if (PyIndex_Check(key)) {                       // 检查key是否是索引Py_ssize_t key_value;key_value = PyNumber_AsSsize_t(key, PyExc_IndexError); if (key_value == -1 && PyErr_Occurred())return -1;return PySequence_SetItem(o, key_value, value);       // 调用索引插入}else if (o->ob_type->tp_as_sequence->sq_ass_item) {type_error("sequence index must be ""integer, not '%.200s'", key);return -1;}}type_error("'%.200s' object does not support item assignment", o);   // 则该类型对象不支持设置return -1;
}

其中就调用了字典的tp_as_mapping的方法集,并调用了该方法集的mp_ass_subscript方法;此时我们分析一下,dict的tp_as_mapping的方法集。此时就调用了tp_as_mapping的mp_ass_subscript方法,此时就是调用dict的dict_ass_sub方法;

static int
dict_ass_sub(PyDictObject *mp, PyObject *v, PyObject *w)
{if (w == NULL)return PyDict_DelItem((PyObject *)mp, v);elsereturn PyDict_SetItem((PyObject *)mp, v, w);
}

可知,删除一个key就是PyDict_DelItem,设置一个key就是PyDict_SetItem;

int
PyDict_SetItem(PyObject *op, PyObject *key, PyObject *value)
{PyDictObject *mp;Py_hash_t hash;if (!PyDict_Check(op)) {            // 检查是否是字典类型PyErr_BadInternalCall();return -1;}assert(key);assert(value);mp = (PyDictObject *)op;if (!PyUnicode_CheckExact(key) ||(hash = ((PyASCIIObject *) key)->hash) == -1)  // 检查传入的key是否hash为-1{hash = PyObject_Hash(key);                      // 生成hash调用key对应的tp_hash方法,在本例中传入的是str类型,则调用str类型的tp_hash方法if (hash == -1)return -1;}/* insertdict() handles any resizing that might be necessary */return insertdict(mp, key, hash, value);          // 生成hash调用key对应的tp_hash方法
}

insertdict方法就是将生成的方法,插入到字典中去;

static int
insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value)
{PyObject *old_value;PyDictKeyEntry *ep;Py_INCREF(key);Py_INCREF(value);if (mp->ma_values != NULL && !PyUnicode_CheckExact(key)) {  if (insertion_resize(mp) < 0)                     // 重新设置mp的大小  如果ma_values有值goto Fail;}Py_ssize_t ix = mp->ma_keys->dk_lookup(mp, key, hash, &old_value);     // 调用查找方法if (ix == DKIX_ERROR)goto Fail;assert(PyUnicode_CheckExact(key) || mp->ma_keys->dk_lookup == lookdict);MAINTAIN_TRACKING(mp, key, value);                                        // 检查mp key values是否需要加入垃圾回收/* When insertion order is different from shared key, we can't share* the key anymore.  Convert this instance to combine table.*/if (_PyDict_HasSplitTable(mp) &&((ix >= 0 && old_value == NULL && mp->ma_used != ix) ||(ix == DKIX_EMPTY && mp->ma_used != mp->ma_keys->dk_nentries))) {  // 检查是否是分离表,如果没查找到旧值并且if (insertion_resize(mp) < 0)                         // 重新设置该字典大小goto Fail;ix = DKIX_EMPTY;}if (ix == DKIX_EMPTY) {/* Insert into new slot. */assert(old_value == NULL);if (mp->ma_keys->dk_usable <= 0) {                      // 如果可用的值小于0/* Need to resize. */if (insertion_resize(mp) < 0)                       // 需要重新扩展字典大小goto Fail;}Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, hash);         // 查找一个可用的hash位置ep = &DK_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries];         // 获取存取的地址dk_set_index(mp->ma_keys, hashpos, mp->ma_keys->dk_nentries);      // 设置该值ep->me_key = key;                                 // 保存keyep->me_hash = hash;                                                   // 保存计算得出的hash值if (mp->ma_values) {                               // 如果mp的ma_values有值assert (mp->ma_values[mp->ma_keys->dk_nentries] == NULL);mp->ma_values[mp->ma_keys->dk_nentries] = value;           // 设置该key对应的value}else {ep->me_value = value;                                             // 直接讲value设置到entry上面}mp->ma_used++;                                   // 使用个数加1mp->ma_version_tag = DICT_NEXT_VERSION();  mp->ma_keys->dk_usable--;                            // 可用减1mp->ma_keys->dk_nentries++;assert(mp->ma_keys->dk_usable >= 0);assert(_PyDict_CheckConsistency(mp));return 0;}if (_PyDict_HasSplitTable(mp)) {                                         // 如果是分离的mp->ma_values[ix] = value;                                           // 直接设置ma_values对应的ix到values中if (old_value == NULL) {/* pending state */assert(ix == mp->ma_used);mp->ma_used++;                               // 使用加1}}else {assert(old_value != NULL);DK_ENTRIES(mp->ma_keys)[ix].me_value = value;}mp->ma_version_tag = DICT_NEXT_VERSION();Py_XDECREF(old_value); /* which **CAN** re-enter (see issue #22653) */assert(_PyDict_CheckConsistency(mp));Py_DECREF(key);return 0;Fail:Py_DECREF(value);Py_DECREF(key);return -1;
}

首先会调用相关的查找方法,去查找待搜索的值是否已经存在字典中,如果当前字典数据已经满了则会按照增长大小的函数生成一个新的字典,并把旧数据设置到新的字典中,当找到的字典匹配时则返回。

其中dk_lookup对应的方法,在初始化之后对应的是lookdict_unicode_nodummy;

/* Faster version of lookdict_unicode when it is known that no <dummy> keys* will be present. */
static Py_ssize_t _Py_HOT_FUNCTION
lookdict_unicode_nodummy(PyDictObject *mp, PyObject *key,Py_hash_t hash, PyObject **value_addr)
{assert(mp->ma_values == NULL);/* Make sure this function doesn't have to handle non-unicode keys,including subclasses of str; e.g., one reason to subclassunicodes is to override __eq__, and for speed we don't cater tothat here. */if (!PyUnicode_CheckExact(key)) {                     // 检查如果不是unicode则直接调用lookdict方法查找mp->ma_keys->dk_lookup = lookdict;return lookdict(mp, key, hash, value_addr);}PyDictKeyEntry *ep0 = DK_ENTRIES(mp->ma_keys);             // 获取keys的首个元素地址size_t mask = DK_MASK(mp->ma_keys);                    // 获取大小size_t perturb = (size_t)hash;size_t i = (size_t)hash & mask;                       // 获取生成的最终的值                 for (;;) {Py_ssize_t ix = dk_get_index(mp->ma_keys, i);                // 便利ma_keys key列表assert (ix != DKIX_DUMMY);                     // 判断不能为空if (ix == DKIX_EMPTY) {                                     // 如果为空则证明找到一个可以使用的*value_addr = NULL;                       // 讲key对应的value设置为空return DKIX_EMPTY;                        // 返回}PyDictKeyEntry *ep = &ep0[ix];                       // 获取该位置元素值                  assert(ep->me_key != NULL);assert(PyUnicode_CheckExact(ep->me_key));if (ep->me_key == key ||(ep->me_hash == hash && unicode_eq(ep->me_key, key))) {  // 如果key相同 hash值也相同*value_addr = ep->me_value;                    // 将该值赋值return ix;}perturb >>= PERTURB_SHIFT;                      // 偏移i = mask & (i*5 + perturb + 1);                   // 获取下一个位置}Py_UNREACHABLE();
}

该函数的主要工作就是查找,字典中是否有空余的值,或者如果找到了满足hash值与key相同的就将value设置为找到的值(这也是字典查找的核心逻辑)。至此,字典的插入的大致流程已经分析完毕。

Python字典的操作测试

现在我们动手观看一下具体的操作实例,首先声明,该例子仅供调试使用,目前调试的字典的key与value都是float类型并且不能del或者pop其中的key。操作字典如下所示;

d = {20000:2}
d[1] = 2
d[3] = 2

首先,讲如下代码插入到dictobject.c的1060行;

        // 测试代码PyObject* key1 = PyLong_FromLong(20000);Py_hash_t hash1 = PyObject_Hash(key1);PyObject* old_value1;Py_ssize_t ix1 = mp->ma_keys->dk_lookup(mp, key1, hash1, &old_value1);if (ix1 == 0){PyLongObject* give;give = (PyLongObject* )key1;printf("found value : %ld\n", give->ob_digit[0]);PyDictKeyEntry *ep01 = DK_ENTRIES(mp->ma_keys);int i, count;count = mp->ma_used;int size_count, j;size_count = mp->ma_keys->dk_size;printf("%s ", mp->ma_keys->dk_indices);int8_t *indices = (int8_t*)(mp->ma_keys->dk_indices);printf("indices index values :");for (j=0; j<size_count;j++){printf("%d  ",(char) indices[j]);}printf("\n");for (i=0; i<count;i++){give = (PyLongObject* )ep01->me_key;printf("size : %d ", mp->ma_keys->dk_size);printf("found value while  key : %ld   ", give->ob_digit[0]);give = (PyLongObject* )ep01->me_value;printf("value  : %ld\n", give->ob_digit[0]);ep01++;}}

然后编译运行;

Python 3.7.3 (default, May 22 2019, 16:17:57)
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> d = {20000:2}
found value : 20000indices index values :0  -1  -1  -1  -1  -1  -1  -1
size : 8 found value while  key : 20000   value  : 2

其中为什么初始化的时候输入20000,是根据代码找到相关的key值,因为字典也被python自身实现的结构中引用了多次,所以我们就设置了一个特殊值来跟踪我们想要的字典;当d初始化的时候,就输出如上所示内容;我们接下来继续操作;

>>> d = {20000:2}
found value : 20000indices index values :0  -1  -1  -1  -1  -1  -1  -1
size : 8 found value while  key : 20000   value  : 2
>>> d[2] = 3
found value : 20000indices index values :0  -1  1  -1  -1  -1  -1  -1
size : 8 found value while  key : 20000   value  : 2
size : 8 found value while  key : 2   value  : 3
>>> d[3] = 4
found value : 20000indices index values :0  -1  1  2  -1  -1  -1  -1
size : 8 found value while  key : 20000   value  : 2
size : 8 found value while  key : 2   value  : 3
size : 8 found value while  key : 3   value  : 4
>>> d[5] = 6
found value : 20000indices index values :0  -1  1  2  -1  3  -1  -1
size : 8 found value while  key : 20000   value  : 2
size : 8 found value while  key : 2   value  : 3
size : 8 found value while  key : 3   value  : 4
size : 8 found value while  key : 5   value  : 6
>>> d[7] = 8
found value : 20000indices index values :0  -1  1  2  -1  3  -1  4
size : 8 found value while  key : 20000   value  : 2
size : 8 found value while  key : 2   value  : 3
size : 8 found value while  key : 3   value  : 4
size : 8 found value while  key : 5   value  : 6
size : 8 found value while  key : 7   value  : 8

此后我们一直添加值进d,从输出信息可知,index就是记录了PyDictKeyEntry的索引值,-1就表示该处未使用。
当我们继续向d中添加内容时;

>>> d[9] = 10
found value : 20000indices index values :0  -1  1  2  -1  3  -1  4  -1  5  -1  -1  -1  -1  -1  -1
size : 16 found value while  key : 20000   value  : 2
size : 16 found value while  key : 2   value  : 3
size : 16 found value while  key : 3   value  : 4
size : 16 found value while  key : 5   value  : 6
size : 16 found value while  key : 7   value  : 8
size : 16 found value while  key : 9   value  : 10
>>> d[10] = 11
found value : 20000indices index values :0  -1  1  2  -1  3  -1  4  -1  5  6  -1  -1  -1  -1  -1
size : 16 found value while  key : 20000   value  : 2
size : 16 found value while  key : 2   value  : 3
size : 16 found value while  key : 3   value  : 4
size : 16 found value while  key : 5   value  : 6
size : 16 found value while  key : 7   value  : 8
size : 16 found value while  key : 9   value  : 10
size : 16 found value while  key : 10   value  : 11

从输出内容可知,字典的大小随之改变了,这也说明了python字典的最佳大小容量限定在1/2到2/3之间,如果超过这个阈值则字典就会自动扩容,扩容的策略大家可详细查看源码。

总结

通过本文对字典的查找与插入,可以大概了解到python中字典的操作流程,有关字典的删除、扩容和释放等内容,以后有机会再继续分析,由于本人水平有限,如有疏漏与错误请大家批评指正。

python3.7源码分析-字典相关推荐

  1. Python3.5源码分析-Dict概述

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的Dict对象 在生成d = {}和d['1'] ...

  2. Python3.5源码分析-List概述

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的List对象 list对象是一个变长对象,在运 ...

  3. Python3.5源码分析-sys模块及site模块导入

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的sys模块初始化 根据分析完成builtins ...

  4. Python3.5源码分析-内建模块builtins初始化

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3模块初始化与加载 Python的模块分为内建的模 ...

  5. python3.5源码分析-启动与虚拟机

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3启动流程概述 本文基于python3分析其基本的 ...

  6. Python3.5源码分析-垃圾回收机制

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的垃圾回收概述 随着软硬件的发展,大多数语言都已 ...

  7. Python3.5源码分析-内存管理

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的内存管理概述 python提供了对内存的垃圾收 ...

  8. python3.7源码分析-集合(set)

    python集合 set是无序且不重复的集合,是可变的,通常用来从列表中删除重复项以及计算数学运算,如交集.并集.差分和对称差分等集合操作.set 支持 x in set, len(set),和 fo ...

  9. Redis 数据结构-字典源码分析

    2019独角兽企业重金招聘Python工程师标准>>> 相关文章 Redis 初探-安装与使用 Redis 数据结构-字符串源码分析 本文将从以下几个方面介绍 前言 字典结构图 字典 ...

最新文章

  1. poco vs Boost
  2. linux内核参数注释与优化
  3. linux系统上传代码到gitlab服务器
  4. 软中断amp;taskletamp;工作队列
  5. Centos7 更新pip和scipy
  6. 雷利子油猴脚本v0.3.6,无需启动天翼客户端直接下载
  7. Azure 5月新发布:CDN图片处理功能, CDN Restful API, 新版CDN管理门户, 计量名称变更延期
  8. 光标的覆盖模式与插入模式
  9. jquery cookie 本地不能工作的原因
  10. python 条件判断和循环
  11. 思维导图哪款好用?怎么借助MindManager 做旅游计划
  12. vs strncpy运行出错_日志VS网络数据,谁能做好全链路监控?
  13. 超详细的Engauge Digitizer 软件教程
  14. 自动驾驶仿真:Carsim、NI和VTD联合仿真课题一
  15. python report_python之Reportlab模块
  16. JMeter proxy server
  17. webpack 图片压缩
  18. gtest 中Setup TearDown SetUpTestCase和TearDownTestCase 的区别
  19. Cursor攻略,吃个螃蟹
  20. 告别手动输入验证码!Web自动化测试带你解锁验证码处理和Cookie机制,跨越测试瓶颈!

热门文章

  1. CT片居然可以这么玩:用头部CT断层扫描片复原三维头像
  2. 花了两天,终于把 Python 的 setup.py 给整明白了
  3. GPT-3到来,程序员会被AI取代吗?
  4. 炸裂!这些大厂跪求的人才太牛了!
  5. 100多次竞赛后,他研发了一个几乎可以解决所有机器学习问题的框架
  6. 为什么华为200万招聘AI博士,马斯克却推出脑机接口对抗AI?
  7. Python编写循环的两个建议 | 鹅厂实战
  8. Github免费中文书《Go入门指南》,带你从零学Go | 极客头条
  9. 只讲技术,拒绝空谈!2018 AI开发者大会精彩议程曝光
  10. 科大讯飞拟募资36亿,要搞什么大事?