7.1. 散列表

散列表的基本思想,是通过一定的函数将需搜索的键值映射为一个整数,将这个整数视为索引值去访问某片连续的内存区域。理论上,在最优情况下,散列表能提供O(1)复杂度的搜索效率。

用于映射的函数称为散列函数(hash function),而映射后的值称为元素的散列值(hash value)。在散列表的实现中,所选择的散列函数的优劣将直接决定所实现的散列表的搜索效率的高低。

在使用散列表的过程中,不同的对象经过散列函数的作用,可能被映射为相同的散列值。而且随着需要存储的数据的增多,这样的冲突就会发生得越来越频繁。散列冲突是散列技术与生俱来的问题。

装载率是散列表中已使用空间和总空间的比值。如果散列表一共可以容纳10个元素,而当前已经装入了6个元素,那么装载率就是6/10。研究表明,当散列表的装载率大于2/3时,散列冲突发生的概率就会大大增加。

当产生散列冲突时,Python会通过一个二次探测函数f,计算下一个候选位置addr,如果位置addr可用,则可将待插入元素放到位置addr;如果位置addr不可用,则Python会再次使用探测函数f,获得下一个候选位置,如此不断探测,总会找到一个可用的位置。

当需要删除某条探测链上的某个元素时,问题就产生了。假如这条链的首元素位置为a,尾元素的位置为c,现在需要删除中间的某个位置b上的元素。如果直接将位置b上的元素删除,则会导致探测链的断裂,从而找不到c。所以,在采用开放定址的冲突解决策略的散列表中,删除某条探测链上的元素时不能进行真正的删除,而是进行一种“伪删除”操作,必须要让该元素还存在于探测链上,担当承前启后的重任。

7.2. Dict对象数据结构

7.2.1. Dict对象的C源码

PyDictObject的C源码如下:

// dictobject.h

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;

/* Dictionary version: globally unique, value change each time the dictionary is modified */

uint64_t ma_version_tag;

PyDictKeysObject *ma_keys;

/* If ma_values is NULL, the table is "combined": keys and values are 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;

} PyDictObject;

PyDictObject结构里最重要的一个属性是ma_keys,在combined table模式下ma_keys存着key和value。ma_keys为PyDictKeysObject 类型,PyDictKeysObject的C源码如下:

// dict-common.h

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;

/* dict_lookup_func() returns index of entry which can be used like DK_ENTRIES(dk)[index].

* -1 when no entry found, -3 when compare raises error.

*/

typedef Py_ssize_t (*dict_lookup_func)

(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr);

/* 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;

/* Function to lookup in the hash table (dk_indices): */

dict_lookup_func dk_lookup;

/* Number of usable entries in dk_entries. */

Py_ssize_t dk_usable;

/* Number of used entries in dk_entries. */

Py_ssize_t dk_nentries;

/* 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, 8 is minimum. */

union {

int8_t as_1[8];

int16_t as_2[4];

int32_t as_4[2];

#if SIZEOF_VOID_P > 4

int64_t as_8[1];

#endif

} dk_indices;

};

7.2.2. PyDictKeysObject内存布局

内存布局如下:

+---------------+

| dk_refcnt |

| dk_size |

| dk_lookup |

| dk_usable |

| dk_nentries |

+---------------+

| dk_indices |

| |

+---------------+

| dk_entries |

| |

+---------------+

其中:

dk_indices是实际的hash表,它保存dk_entries中的index,或DKIX_EMPTY(-1),或DKIX_DUMMY(-2)。dk_indices的size为dk_size。

dk_size不同,index的类型也不同,如下表。值得注意的是,由于-1和-2有特殊的含义,所以索引必须是有符号整数,所以dk_size <= 128的时候使用int8,而不是dk_size <=256的时候使用int8。

int8 for dk_size <= 128

int16 for 256 <= dk_size <= 2**15

int32 for 2**16 <= dk_size <= 2**31

int64 for 2**32 <= dk_size

dk_entries是PyDictKeyEntry类型的数组,数组的size是USABLE_FRACTION(dk_size),即可dk_size * 2/3。通过DK_ENTRIES(dk)可以获取dk_entries的指针。

7.2.3. PyDictKeysObject的两种形式

分为combined table和split table:

combined table:ma_values == NULL,dk_refcnt == 1,ma_keys存储key和值;

split table:ma_values != NULL,dk_refcnt >= 1,ma_keys存储key,me_value数组里存储值;

下面的分析基于combined table。

7.3. Dict对象

Dict对象是“变长对象”。

7.3.1. Python中的创建

Python中Dict对象最重要的创建方法为PyDict_New,如下Python语句最终会调用到PyDict_New:

test = {1:'hello'}

7.3.2. PyDict_New的C调用栈

// compile.c

PyAST_CompileObject

// symtable.c

=>PySymtable_BuildObject

=>symtable_new

// dictobject.c

=> PyDict_New

7.3.3. PyDict_New源码

// dictobject.c

#define USABLE_FRACTION(n) (((n) << 1)/3)

#define PyDict_MINSIZE 8

#define DK_SIZE(dk) ((dk)->dk_size)

#define DK_IXSIZE(dk) \

(DK_SIZE(dk) <= 0xff ? \

1 : DK_SIZE(dk) <= 0xffff ? \

2 : DK_SIZE(dk) <= 0xffffffff ? \

4 : sizeof(int64_t))

#define DK_ENTRIES(dk) \

((PyDictKeyEntry*)(&(dk)->dk_indices.as_1[DK_SIZE(dk) * DK_IXSIZE(dk)]))

PyObject *

PyDict_New(void)

{

PyDictKeysObject *keys = new_keys_object(PyDict_MINSIZE);

if (keys == NULL)

return NULL;

return new_dict(keys, NULL);

}

new_keys_object用于创建PyDictKeysObject对象。计算usable,如果缓冲区没有可用的PyDictKeysObject对象则计算PyDictKeysObject对象的实际大小并分配内存,然后各种设置,最后设置查找函数lookdict_unicode_nodummy,并memset内存。源码如下:

// dictobject.c

static PyDictKeysObject *new_keys_object(Py_ssize_t size)

{

PyDictKeysObject *dk;

Py_ssize_t es, usable;

assert(size >= PyDict_MINSIZE);

assert(IS_POWER_OF_2(size));

usable = USABLE_FRACTION(size);

if (size <= 0xff) {

es = 1;

}

else if (size <= 0xffff) {

es = 2;

}

#if SIZEOF_VOID_P > 4

else if (size <= 0xffffffff) {

es = 4;

}

#endif

else {

es = sizeof(Py_ssize_t);

}

if (size == PyDict_MINSIZE && numfreekeys > 0) {

dk = keys_free_list[--numfreekeys];

}

else {

dk = PyObject_MALLOC(sizeof(PyDictKeysObject)

- Py_MEMBER_SIZE(PyDictKeysObject, dk_indices)

+ 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.as_1[0], 0xff, es * size);

memset(DK_ENTRIES(dk), 0, sizeof(PyDictKeyEntry) * usable);

return dk;

}

new_dict创建PyDictObject 对象,PyDictKeysObject对象的壳。如果缓冲区没有可用的PyDictObject对象则分配内存,并且各种设置。源码如下:

// dictobject.c

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;

mp->ma_version_tag = DICT_NEXT_VERSION();

assert(_PyDict_CheckConsistency(mp));

return (PyObject *)mp;

}

可以看到:

PyDictKeysObject对象缓冲:通过操作numfreekeys实现,numfreekeys在free_keys_object和dictresize方法中进行调整。

// dictobject.c

#define PyDict_MAXFREELIST 80

static PyDictKeysObject *keys_free_list[PyDict_MAXFREELIST];

static int numfreekeys = 0;

PyDictObject对象缓冲:通过操作numfree实现,numfree在dict_dealloc方法中进行调整。

// dictobject.c

#define PyDict_MAXFREELIST 80

static PyDictObject *free_list[PyDict_MAXFREELIST];

static int numfree = 0;

7.4. Dict对象的查找

Dict对象的查找是Dict对象最重要的方法。Python Dict对象默认的查找方法为lookdict_unicode_nodummy,在lookdict_unicode_nodummy方法里会判断如果key不是unicode类型,则将查找方法设置为lookdict,并调用lookdict进行查找。

lookdict_unicode_nodummy与lookdict最重要的区别在于,hash相同的情况下对key的比对,lookdict_unicode_nodummy调用的unicode_eq,而lookdict调用的PyObject_RichCompareBool。由于Python源码实现中大量的使用string作为key的dict对象,所以这是一项优化。但是对于整个查找机制而言,只要分析明白其中一个方法即可。

lookdict_unicode_nodummy源码如下:

// dictobject.c

#define DK_MASK(dk) (((dk)->dk_size)-1)

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);

if (!PyUnicode_CheckExact(key)) {

mp->ma_keys->dk_lookup = lookdict;

return lookdict(mp, key, hash, value_addr);

}

PyDictKeyEntry *ep0 = DK_ENTRIES(mp->ma_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);

assert (ix != DKIX_DUMMY);

if (ix == DKIX_EMPTY) {

*value_addr = NULL;

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))) {

*value_addr = ep->me_value;

return ix;

}

perturb >>= PERTURB_SHIFT;

i = mask & (i*5 + perturb + 1);

}

Py_UNREACHABLE();

}

lookdict_unicode_nodummy中通过:

i = (size_t)hash & mask;

Py_ssize_t ix = dk_get_index(dk, i);

计算i在dk_entries中的索引。

如果该索引:

1. DKIX_EMPTY,即没有找到,则value=NULL,并返回索引值(DKIX_EMPTY);

2. DKIX_DUMMY,跳转到4;

3. 索引值ix >= 0:

3.1. 如果key对象完全一致,则返回value和索引值;

3.2. 如果hash值一致,则调用unicode_eq或PyObject_RichCompareBool 比较key

3.2.1. 如果key相等,则返回value和索引;

3.2.2. 否则跳转到4;

3.3. 否则跳转到4

4. 根据探测函数(i = mask & (i*5 + perturb + 1))计算下一个ix;

所以lookdict_unicode_nodummy方法:

1. 要么返回NULL和DKIX_EMPTY;

2. 要么返回value和索引;

7.5. Dict对象的维护

7.5.1. 设置/添加元素

如下Python语句最终会调用到PyDict_SetItem:

test = {100: 200}

PyDict_SetItem的C调用栈:

// pystate.c

PyInterpreterState_New

// ceval.c

=>_PyEval_EvalFrameDefault (case BUILD_MAP)

// dictobject.c

=> PyDict_SetItem

PyDict_SetItem源码:

// dictobject.c

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)

{

hash = PyObject_Hash(key);

if (hash == -1)

return -1;

}

/* insertdict() handles any resizing that might be necessary */

return insertdict(mp, key, hash, value);

}

PyDict_SetItem方法中做了一下检查,调用insertdict方法:

// dictobject.c

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)

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);

/* 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) {

/* Need to resize. */

if (insertion_resize(mp) < 0)

goto Fail;

}

Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, 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;

ep->me_hash = hash;

if (mp->ma_values) {

assert (mp->ma_values[mp->ma_keys->dk_nentries] == NULL);

mp->ma_values[mp->ma_keys->dk_nentries] = value;

}

else {

ep->me_value = value;

}

mp->ma_used++;

mp->ma_version_tag = DICT_NEXT_VERSION();

mp->ma_keys->dk_usable--;

mp->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;

if (old_value == NULL) {

/* pending state */

assert(ix == mp->ma_used);

mp->ma_used++;

}

}

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;

}

insertdict中调用lookdict_unicode_nodummy或lookdict方法寻找Dice对象:

1. 没有找到key

1.1. 检查是否已经用完空间,如果用完,调用insertion_resize调整dict大小;

1.2. 调用find_empty_slot方法寻找探测链上第一个为DKIX_DUMMY或DKIX_EMPTY的,设置索引值,各种设置和调整

2. 否则设置value即可

find_empty_slot方法源码如下:

// dictobject.c

static Py_ssize_t

find_empty_slot(PyDictKeysObject *keys, Py_hash_t hash)

{

assert(keys != NULL);

const size_t mask = DK_MASK(keys);

size_t i = hash & mask;

Py_ssize_t ix = dk_get_index(keys, i);

for (size_t perturb = hash; ix >= 0;) {

perturb >>= PERTURB_SHIFT;

i = (i*5 + perturb + 1) & mask;

ix = dk_get_index(keys, i);

}

return i;

}

7.5.2. 删除元素

如下Python语句最终会调用到PyDict_DelItem:

test = {100: 200}

del test[100]

PyDict_SetItem的C调用栈:

// dictobject.c

dict_ass_sub

=> PyDict_DelItem

PyDict_SetItem源码:

// dictobject.c

int

PyDict_DelItem(PyObject *op, PyObject *key)

{

Py_hash_t hash;

assert(key);

if (!PyUnicode_CheckExact(key) ||

(hash = ((PyASCIIObject *) key)->hash) == -1) {

hash = PyObject_Hash(key);

if (hash == -1)

return -1;

}

return _PyDict_DelItem_KnownHash(op, key, hash);

}

PyDict_SetItem方法中做了一下检查,调用_PyDict_DelItem_KnownHash方法:

// dictobject.c

int

_PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash)

{

Py_ssize_t ix;

PyDictObject *mp;

PyObject *old_value;

if (!PyDict_Check(op)) {

PyErr_BadInternalCall();

return -1;

}

assert(key);

assert(hash != -1);

mp = (PyDictObject *)op;

ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &old_value);

if (ix == DKIX_ERROR)

return -1;

if (ix == DKIX_EMPTY || old_value == NULL) {

_PyErr_SetKeyError(key);

return -1;

}

// Split table doesn't allow deletion. Combine it.

if (_PyDict_HasSplitTable(mp)) {

if (dictresize(mp, DK_SIZE(mp->ma_keys))) {

return -1;

}

ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &old_value);

assert(ix >= 0);

}

return delitem_common(mp, hash, ix, old_value);

}

查找key,查找到了调用delitem_common。需要注意的是查找到了,只是把状态设置为DKIX_DUMMY, 并没有从探测链上摘除。delitem_common源码如下:

// dictobject.c

static int

delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix,

PyObject *old_value)

{

PyObject *old_key;

PyDictKeyEntry *ep;

Py_ssize_t hashpos = lookdict_index(mp->ma_keys, hash, ix);

assert(hashpos >= 0);

mp->ma_used--;

mp->ma_version_tag = DICT_NEXT_VERSION();

ep = &DK_ENTRIES(mp->ma_keys)[ix];

dk_set_index(mp->ma_keys, hashpos, DKIX_DUMMY);

ENSURE_ALLOWS_DELETIONS(mp);

old_key = ep->me_key;

ep->me_key = NULL;

ep->me_value = NULL;

Py_DECREF(old_key);

Py_DECREF(old_value);

assert(_PyDict_CheckConsistency(mp));

return 0;

}

static Py_ssize_t

lookdict_index(PyDictKeysObject *k, Py_hash_t hash, Py_ssize_t index)

{

size_t mask = DK_MASK(k);

size_t perturb = (size_t)hash;

size_t i = (size_t)hash & mask;

for (;;) {

Py_ssize_t ix = dk_get_index(k, i);

if (ix == index) {

return i;

}

if (ix == DKIX_EMPTY) {

return DKIX_EMPTY;

}

perturb >>= PERTURB_SHIFT;

i = mask & (i*5 + perturb + 1);

}

Py_UNREACHABLE();

}

7.5.3. 调整大小

无论是设置、插入还是删除,在满足一定条件下(参见上面的代码分析),都会调用insertion_resize。insertion_resize方法会重新创建PyDictKeysObject对象,在这个过程中会拷贝旧的对象所有的数据。

// dictobject.c

#define GROWTH_RATE(d) (((d)->ma_used*2)+((d)->ma_keys->dk_size>>1))

static int

insertion_resize(PyDictObject *mp)

{

return dictresize(mp, GROWTH_RATE(mp));

}

static int

dictresize(PyDictObject *mp, Py_ssize_t minsize)

{

Py_ssize_t newsize, numentries;

PyDictKeysObject *oldkeys;

PyObject **oldvalues;

PyDictKeyEntry *oldentries, *newentries;

/* Find the smallest table size > minused. */

for (newsize = PyDict_MINSIZE;

newsize < minsize && newsize > 0;

newsize <<= 1)

;

if (newsize <= 0) {

PyErr_NoMemory();

return -1;

}

oldkeys = mp->ma_keys;

/* Allocate a new table. */

mp->ma_keys = new_keys_object(newsize);

if (mp->ma_keys == NULL) {

mp->ma_keys = oldkeys;

return -1;

}

// New table must be large enough.

assert(mp->ma_keys->dk_usable >= mp->ma_used);

if (oldkeys->dk_lookup == lookdict)

mp->ma_keys->dk_lookup = lookdict;

numentries = mp->ma_used;

oldentries = DK_ENTRIES(oldkeys);

newentries = DK_ENTRIES(mp->ma_keys);

oldvalues = mp->ma_values;

if (oldvalues != NULL) {

/* Convert split table into new combined table.

* We must incref keys; we can transfer values.

* Note that values of split table is always dense.

*/

for (Py_ssize_t i = 0; i < numentries; i++) {

assert(oldvalues[i] != NULL);

PyDictKeyEntry *ep = &oldentries[i];

PyObject *key = ep->me_key;

Py_INCREF(key);

newentries[i].me_key = key;

newentries[i].me_hash = ep->me_hash;

newentries[i].me_value = oldvalues[i];

}

DK_DECREF(oldkeys);

mp->ma_values = NULL;

if (oldvalues != empty_values) {

free_values(oldvalues);

}

}

else { // combined table.

if (oldkeys->dk_nentries == numentries) {

memcpy(newentries, oldentries, numentries * sizeof(PyDictKeyEntry));

}

else {

PyDictKeyEntry *ep = oldentries;

for (Py_ssize_t i = 0; i < numentries; i++) {

while (ep->me_value == NULL)

ep++;

newentries[i] = *ep++;

}

}

assert(oldkeys->dk_lookup != lookdict_split);

assert(oldkeys->dk_refcnt == 1);

if (oldkeys->dk_size == PyDict_MINSIZE &&

numfreekeys < PyDict_MAXFREELIST) {

DK_DEBUG_DECREF keys_free_list[numfreekeys++] = oldkeys;

}

else {

DK_DEBUG_DECREF PyObject_FREE(oldkeys);

}

}

build_indices(mp->ma_keys, newentries, numentries);

mp->ma_keys->dk_usable -= numentries;

mp->ma_keys->dk_nentries = numentries;

return 0;

}

由于扩/缩容,所以要调整索引,调用build_indices方法:

// dictobject.c

static void

build_indices(PyDictKeysObject *keys, PyDictKeyEntry *ep, Py_ssize_t n)

{

size_t mask = (size_t)DK_SIZE(keys) - 1;

for (Py_ssize_t ix = 0; ix != n; ix++, ep++) {

Py_hash_t hash = ep->me_hash;

size_t i = hash & mask;

for (size_t perturb = hash; dk_get_index(keys, i) != DKIX_EMPTY;) {

perturb >>= PERTURB_SHIFT;

i = mask & (i*5 + perturb + 1);

}

dk_set_index(keys, i, ix);

}

}

7.6. Dict对象的特性

支持tp_as_sequence、tp_as_mapping两种操作。

7.6.1. 序列操作

// dictobject.c

&dict_as_sequence, /* tp_as_sequence */

// dictobject.c

static PySequenceMethods dict_as_sequence = {

0, /* sq_length */

0, /* sq_concat */

0, /* sq_repeat */

0, /* sq_item */

0, /* sq_slice */

0, /* sq_ass_item */

0, /* sq_ass_slice */

PyDict_Contains, /* sq_contains */

0, /* sq_inplace_concat */

0, /* sq_inplace_repeat */

};

其中:

PyDict_Contains

test = {200:100}

200 in test # True

100 in test # False

7.6.2. 关联操作

// dictobject.c

&dict_as_mapping, /* tp_as_mapping */

// dictobject.c

static PyMappingMethods dict_as_mapping = {

(lenfunc)dict_length, /*mp_length*/

(binaryfunc)dict_subscript, /*mp_subscript*/

(objobjargproc)dict_ass_sub, /*mp_ass_subscript*/

};

其中:

dict_length

test = {200:100}

len(test)

dict_subscript

test = {200:100}

test[200]

dict_ass_sub

test = {}

test[200] = 100

7.6.3. to string

// dictobject.c

(reprfunc)dict_repr, /* tp_repr */

0, /* tp_str */

7.6.4. hash

// dictobject.c

PyObject_HashNotImplemented, /* tp_hash */

7.6.5. 比较

// dictobject.c

dict_richcompare, /* tp_richcompare */

7.6.6. 内置方法

// dictobject.c

mapp_methods, /* tp_methods */

7.7. 参考

Python源码剖析

python3 源码_7. Python3源码—Dict对象-阿里云开发者社区相关推荐

  1. python3 模板库_3 个 Python 模板库比较-阿里云开发者社区

    在我的日常工作中,我花费大量的时间将各种来源的数据转化为可读的信息.虽然很多时候这只是电子表格或某种类型的图表或其他数据可视化的形式,但也有其他时候,将数据以书面形式呈现是有意义的. 但我的头疼地方就 ...

  2. oss 视频 转码_播放oss问题的搜索结果-阿里云开发者社区

    OSS 咨询 作者:张医博 浅谈 本章介绍一些基本的 oss 的咨询内容,问题是一直在补充的,不定期的进行更新: 问:cdn 结合 oss,oss 地址被暴露后如何处理 答: 暴露后无法组织别人访问你 ...

  3. python集合可以修改吗_修改包含Python3中的集合的集合列表-问答-阿里云开发者社区-阿里云...

    我试图创建一个以元组为元素的列表.每个元组都有4个整数.前两个整数是对2个range进行压缩的结果,而其他2个则是对2个不同的整数进行压缩的结果. 我正在使用此代码创建元组和最终列表,这些列表是从笛卡 ...

  4. mysql 5.7.14编译安装_源码编译安装mysql-5.7.14-阿里云开发者社区

    1.下载并上传 mysql-5.7.14.tar.gzview plain tar -zxf msql-5.7.14.tar.gz yum -y install gcc gcc-c++ ncurses ...

  5. C/C++开发,基于IBM纯 c标准MQTT源码,实现纯C语言访问阿里云物联网平台开发案例

    目录 一.MQTT协议简介 二.阿里云物联网平台 2.1  阿里云平台及公共实例

  6. window安装python3后怎么用pyspark_在windows上面安装并用jupyter运行pyspark-阿里云开发者社区...

    intro 首先,这里介绍的方法很基本,大牛可以绕过了. 由于个人的测试需要,打算在自己的pc上面安pyspark,来熟悉MLlib. 翻了一下bing和狗,方法不少,试了linux虚拟机安装,vmw ...

  7. 【阿里云镜像】使用VM虚拟机安装OpenWRT并更换阿里云镜像源

    文章目录 一.参考链接 二.OpenWRT简介 三.使用VM虚拟机安装OpenWRT操作步骤 1.下载OpenWRT.img镜像源 2.将.img镜像文件转换成.vmdk虚拟硬盘格式文件 (1)打开S ...

  8. 2022阿里云码上公益“第益课”大学生技术公益实践计划活动说明

    简介:简介: 2022年,码上公益联合共青团杭州市余杭区团委.杭州师范大学.浙江工商大学.浙江大学.阿里巴巴公益.阿里云开发者学堂.宜搭发起"第益课"高校技术公益实践计划,为高校学 ...

  9. 香橙派mqtt阿里云终端(二) --python3.5环境配置以及阿里云环境测试

    书接上回,我搭好了树莓派所需的环境以及往我的个人服务网站上添加了我的树莓派终端,过程当中也是踩了不少坑,不过总算是过来了.接下来切入正题. 目录 python环境以及包配置 基础包安装 阿里云服务器配 ...

最新文章

  1. 海康威视连续采图与单步采图_c#
  2. 将vim打造成IDE编程环境
  3. Html5 dragdrop
  4. 如何解决光端机的开关量问题?
  5. 初中文化能学编程吗_网页编程课程来了,确定不来pick一下!!!|科创辅学进行时...
  6. Java+sql server+CallableStatement调用存储过程三种情况 (转)
  7. jquery修改样式通过类
  8. UVALive - 8295 Triangle to Hexagon
  9. 方舟php服务器控制,方舟基本管理命令代码
  10. oracle密码已过期问题解决
  11. Excel2003进制转换方法
  12. aaaa555555555
  13. 科学计算机求n次方,科学计算器怎么算n次方_科学计算器的度分秒怎么按
  14. 新型冠状病毒SIR预测模型,MATLAB代码
  15. ARVR | 5大AR应用程序开发工具简介
  16. java 对接第三方接口
  17. 如何将本地图片转成图片链接
  18. 7月书讯 (上)| 宝藏新书助你“乘风破浪”
  19. ASP.NET 母版页(嵌套、访问母版页的控件和属性)
  20. 如何使用扫描仪扫描文字

热门文章

  1. azure云数据库_使用Azure SQL数据库构建ASP.NET应用
  2. 使用数据库维护计划SQL Server更新统计信息
  3. C++之路进阶codevs1242(布局)
  4. Dreamweaver里文档工具栏位置
  5. 【转】Android 避免APP启动闪黑屏(Theme和Style)
  6. 北京科技大学计算机专业博导,北京科技大学计算机与通信工程学院-班晓娟
  7. matlab ode45 初值,为什么设定的初值在ode45里无效?
  8. Redis事务与MySQL事务的区别
  9. HTTP协议的无状态性
  10. 获得国内中国电信,网通,铁通的最新ip段的方法