自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

字典是怎么創(chuàng)建的,支持的操作又是如何實(shí)現(xiàn)的?

開發(fā) 前端
關(guān)于字典是怎么創(chuàng)建的,以及它添加鍵值對、基于鍵獲取值的源碼細(xì)節(jié),我們就分析完了。當(dāng)然還沒有結(jié)束,字典還有很多的自定義方法,我們下一篇文章來剖析這些自定義方法的實(shí)現(xiàn)細(xì)節(jié)。?

楔子

到目前為止,我們對字典應(yīng)該已經(jīng)有了細(xì)致的了解了,本篇文章來聊一聊字典的創(chuàng)建和相關(guān)操作,通過底層的源碼實(shí)現(xiàn),來進(jìn)一步剖析字典。

字典的創(chuàng)建

字典在底層對應(yīng) PyDictObject 實(shí)例,它是怎么創(chuàng)建的呢?解釋器提供了 PyDict_New 函數(shù),會(huì)創(chuàng)建一個(gè)容量為 8 的字典。

// Objects/dictobject.c

// 對于結(jié)合表,鍵值對均由 PyDictKeysObject 維護(hù)
// 它一旦被創(chuàng)建,那么 dk_indices 的長度至少是 8
// 至于 dk_indices 里面的元素初始為 -1,表示哈希槽尚未被使用
static PyDictKeysObject empty_keys_struct = {
    _Py_IMMORTAL_REFCNT, /* dk_refcnt */
    0,                   /* dk_log2_size */
    0,                   /* dk_log2_index_bytes */
    DICT_KEYS_UNICODE,   /* dk_kind */
    1,                   /* dk_version */
    0,                   /* dk_usable (immutable) */
    0,                   /* dk_nentries */
    {DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY,
     DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY}, /* dk_indices */
};

#define Py_EMPTY_KEYS &empty_keys_struct


PyObject *
PyDict_New(void)
{
    PyInterpreterState *interp = _PyInterpreterState_GET();
    return new_dict(interp, Py_EMPTY_KEYS, NULL, 0, 0);
}

static PyObject *
new_dict(PyInterpreterState *interp,
         PyDictKeysObject *keys, PyDictValues *values,
         Py_ssize_t used, int free_values_on_failure)
{
   // 解釋一下相關(guān)參數(shù)
   /* interp: 進(jìn)程狀態(tài)對象
    * keys: PyDictKeysObject 實(shí)例
    * values: 維護(hù)字典的值,如果是結(jié)合表,那么為 NULL
    *         所以 PyDict_New 創(chuàng)建的是結(jié)合表
    * used: 鍵值對的個(gè)數(shù),初始為 0
    */
    // 指向創(chuàng)建的字典
    PyDictObject *mp;
    assert(keys != NULL);
    // 字典也有緩存池,關(guān)于緩存池我們之后再說,這里先不管
#if PyDict_MAXFREELIST > 0
    struct _Py_dict_state *state = get_dict_state(interp);
    if (state->numfree) {
        mp = state->free_list[--state->numfree];
        assert (mp != NULL);
        assert (Py_IS_TYPE(mp, &PyDict_Type));
        OBJECT_STAT_INC(from_freelist);
        _Py_NewReference((PyObject *)mp);
    }
    else
#endif
    {
        // 為 PyDictObject 對象申請內(nèi)存
        mp = PyObject_GC_New(PyDictObject, &PyDict_Type);
        // 由于是先為 PyDictKeysObject 申請內(nèi)存
        // 所以當(dāng) PyDictObject 的內(nèi)存申請失敗時(shí),還要處理 PyDictKeysObject
        if (mp == NULL) {
            dictkeys_decref(interp, keys);
            if (free_values_on_failure) {
                free_values(values);
            }
            return NULL;
        }
    }
    // 字段初始化,而 keys 和 values 都是外界提前創(chuàng)建好,然后傳過來的
    mp->ma_keys = keys;
    mp->ma_values = values;
    mp->ma_used = used;
    mp->ma_version_tag = DICT_NEXT_VERSION(interp);
    ASSERT_CONSISTENT(mp);
    // 返回字典
    return (PyObject *)mp;
}

所以整個(gè)過程分為兩步:

  • 先創(chuàng)建 PyDictKeysObject 實(shí)例(如果是分離表,那么還要?jiǎng)?chuàng)建 PyDictValues 實(shí)例),底層默認(rèn)提供了一個(gè) Py_EMPTY_KEYS。
  • 再創(chuàng)建 PyDictObject 實(shí)例,然后通過 ma_keys 字段使兩者建立聯(lián)系。

PyDictObject 實(shí)例的創(chuàng)建過程我們已經(jīng)知道了,接下來是 PyDictKeysObject 實(shí)例的創(chuàng)建,只有它創(chuàng)建了,才能作為參數(shù)傳遞給 new_dict 函數(shù)。

// Objects/dictobject.c

static PyDictKeysObject*
new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode)
{   
    PyDictKeysObject *dk;
    Py_ssize_t usable;
    int log2_bytes;
    // entry 的大小
    // 如果 key 全部是字符串,那么大小為 16 字節(jié),否則是 24 字節(jié)
    size_t entry_size = unicode ? sizeof(PyDictUnicodeEntry) \
                        : sizeof(PyDictKeyEntry);

    assert(log2_size >= PyDict_LOG_MINSIZE);
    // USABLE_FRACTION((size_t)1<<log2_size) 表示鍵值對數(shù)組的長度
    // 它等于哈希索引數(shù)組長度的 2/3
    usable = USABLE_FRACTION((size_t)1<<log2_size);
    // 1 << log2_size 表示哈希索引數(shù)組的長度
    // 1 << log2_bytes 表示哈希索引數(shù)組的內(nèi)存大小
    // 如果 log2_size < 8,即 (1 << log2_size) < 256
    // 那么哈希索引數(shù)組中,每個(gè)元素占 1 字節(jié)
    // 此時(shí) (1 << log2_bytes) == (1 << log2_size)
    // 所以將 log2_size 賦值給 log2_bytes
    if (log2_size < 8) {
        log2_bytes = log2_size;
    }
    // 如果 256 <= (1 << log2_size) < 65536
    // 那么哈希索引數(shù)組中,每個(gè)元素占 2 字節(jié)
    // 此時(shí) (1 << log2_bytes) == (1 << log2_size) * 2
    // 而 (1 << log2_size) * 2 等價(jià)于 (1 << (log2_size + 1))
    // 所以 log2_bytes = log2_size + 1
    else if (log2_size < 16) {
        log2_bytes = log2_size + 1;
    }
    // 此時(shí)哈希索引數(shù)組每個(gè)元素占 8 字節(jié)
    // (1 <= log2_bytes) == (1 << log2_size) * 2 * 2 * 2
    // 所以 log2_bytes = log2_size + 3
    else if (log2_size >= 32) {
        log2_bytes = log2_size + 3;
    }
    // 否則說明哈希索引數(shù)組每個(gè)元素占 4 字節(jié)
    // (1 <= log2_bytes) == (1 << log2_size) * 2 * 2
    // 所以 log2_bytes = log2_size + 2
    else {
        log2_bytes = log2_size + 2;
    }
    
    // 不僅是 PyDictObject,PyDictKeysObject 同樣也有自己的緩存池
    // 關(guān)于它的緩存池,同樣之后再聊,這里先不關(guān)心
#if PyDict_MAXFREELIST > 0
    struct _Py_dict_state *state = get_dict_state(interp);
    if (log2_size == PyDict_LOG_MINSIZE && unicode 
     && state->keys_numfree > 0) {
        dk = state->keys_free_list[--state->keys_numfree];
        OBJECT_STAT_INC(from_freelist);
    }
    else
#endif
    {
        // 為 PyDictKeysObject 申請內(nèi)存,當(dāng)然還包括兩個(gè)數(shù)組
        // 哈希索引數(shù)組的內(nèi)存大小為 1 << log2_bytes
        // 鍵值對數(shù)組的大小為 entry_size * usable
        dk = PyObject_Malloc(sizeof(PyDictKeysObject)
                             + ((size_t)1 << log2_bytes)
                             + entry_size * usable);
        if (dk == NULL) {
            PyErr_NoMemory();
            return NULL;
        }
    }
    // 字段初始化
    dk->dk_refcnt = 1;
    dk->dk_log2_size = log2_size;
    dk->dk_log2_index_bytes = log2_bytes;
    dk->dk_kind = unicode ? DICT_KEYS_UNICODE : DICT_KEYS_GENERAL;
    dk->dk_nentries = 0;
    dk->dk_usable = usable;
    dk->dk_version = 0;
    // memset 是一個(gè) C 庫函數(shù):memset(p, val, size)
    // 作用是從指針 p 開始,將之后的 size 個(gè)字節(jié)的值全部初始化為 val
    // 顯然這里是將哈希索引數(shù)組的元素都設(shè)置為 -1,注:(char)0xff == -1
    memset(&dk->dk_indices[0], 0xff, ((size_t)1 << log2_bytes));
    // 將鍵值對數(shù)組中每個(gè) entry 的字段都設(shè)置為 0
    // entry 的內(nèi)存已經(jīng)申請了,但還沒有保存任何的鍵值對
    // 所以將 me_hash、me_key、me_value 全部設(shè)置為 0
    // 注:對于指針類型來說,賦值為 0 和 NULL 是等價(jià)的,因?yàn)?NULL 保存的地址就是 0
    memset(&dk->dk_indices[(size_t)1 << log2_bytes], 0, entry_size * usable);
    return dk;
}

以上就是 PyDictKeysObject 實(shí)例的創(chuàng)建,當(dāng)它創(chuàng)建完畢后,再作為參數(shù)傳遞給 new_dict 函數(shù)創(chuàng)建 PyDictObject 實(shí)例,整個(gè)過程還是比較簡單的。

字典都有哪些方法?

首先類型對象定義了三個(gè)方法簇:

  • tp_as_number:實(shí)例對象作為數(shù)值型對象擁有的方法;
  • tp_as_sequence:實(shí)例對象作為序列型對象擁有的方法;
  • tp_as_mapping:實(shí)例對象作為映射型對象擁有的方法;

當(dāng)然啦,這三個(gè)方法簇對實(shí)例對象的類型要求并不嚴(yán)格,比如字符串作為序列型對象,也可以實(shí)現(xiàn) tp_as_number,比如字符串實(shí)現(xiàn)了里面的取模運(yùn)算符,用于格式化。

那么字典呢,它的這幾個(gè)方法簇都定義了哪些方法呢?

// object/dictobject.c
static PyNumberMethods dict_as_number = {
    .nb_or = dict_or,
    .nb_inplace_or = dict_ior,
};

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

static PyMappingMethods dict_as_mapping = {
    (lenfunc)dict_length, /*mp_length*/
    (binaryfunc)dict_subscript, /*mp_subscript*/
    (objobjargproc)dict_ass_sub, /*mp_ass_subscript*/
};

以上就是字典的幾個(gè)方法簇,我們從 Python 的角度來演示一下。

# dict_as_number.nb_or:用于合并兩個(gè)字典
d1 = {"a": 1, "b": 2}
d2 = {"c": 3, "d": 4}
print(d1 | d2)
"""
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
"""
# 等價(jià)于如下
print({**d1, **d2})
"""
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
"""

# dict_as_number.nb_inplace_or:更新字典
d1 |= d2  # 等價(jià)于 d1.update(d2)
print(d1)
"""
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
"""

# dict_as_sequence.sq_contains:判斷 key 是否存在
print("a" in d1)
"""
True
"""

# dict_as_mapping.dict_length:返回字典長度
print(len(d1))
"""
4
"""

# dict_as_mapping.dict_subscript:基于 key 獲取 value
print(d1["a"])
"""
1
"""

# dict_as_mapping.dict_ass_sub:設(shè)置 key、value
d1["高老師"] = "美男子"
print(d1["高老師"])
"""
美男子
"""

以上三個(gè)方法簇是很多對象共有的,里面的每一個(gè) C 函數(shù)都對應(yīng) Python 的一個(gè)魔法方法,比如:

  • dict_as_number.nb_or 對應(yīng) Python 的 __or__。
  • dict_as_mapping.mp_subscript 對應(yīng) Python 的 __getitem__。
  • dict_as_mapping.mp_ass_subscript 對應(yīng) Python 的 __setitem__。

接下來我們就從源碼的角度,來看看這些方法是怎么實(shí)現(xiàn)的。

設(shè)置鍵值對

設(shè)置鍵值對,比如 d["a"] = 1,那么會(huì)調(diào)用 dict_as_mapping.mp_ass_subscript,看一下它的具體邏輯。

// Objects/dictobject.c
static int
dict_ass_sub(PyDictObject *mp, PyObject *v, PyObject *w)
{   
    // 參數(shù) mp 指向字典,參數(shù) v 指向 key,參數(shù) w 指向 value
    // 雖然是設(shè)置鍵值對,但如果 w == NULL,那么也可以實(shí)現(xiàn)刪除的效果
    if (w == NULL)
        return PyDict_DelItem((PyObject *)mp, v);
    else
        return PyDict_SetItem((PyObject *)mp, v, w);
}


int
PyDict_SetItem(PyObject *op, PyObject *key, PyObject *value)
{
    // op 必須指向字典
    if (!PyDict_Check(op)) {
        PyErr_BadInternalCall();
        return -1;
    }
    assert(key);
    assert(value);
    // Py_NewRef(obj) 會(huì)增加 obj 指向?qū)ο蟮囊糜?jì)數(shù),并返回 obj
    // 所以這里將 key、value 的引用計(jì)數(shù)加 1 之后,又調(diào)用了 _PyDict_SetItem_Take2
    return _PyDict_SetItem_Take2((PyDictObject *)op,
                                 Py_NewRef(key), Py_NewRef(value));
}

int
_PyDict_SetItem_Take2(PyDictObject *mp, PyObject *key, PyObject *value)
{
    assert(key);
    assert(value);
    assert(PyDict_Check(mp));
    Py_hash_t hash;
    // 如果 key 不是字符串,或者 key 是字符串、但哈希值等于 -1(尚未計(jì)算)
    // 那么計(jì)算哈希值
    if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) {
        hash = PyObject_Hash(key);
        if (hash == -1) {
            Py_DECREF(key);
            Py_DECREF(value);
            return -1;
        }
    }
    PyInterpreterState *interp = _PyInterpreterState_GET();
    // 如果是一個(gè)空字典,那么調(diào)用 insert_to_emptydict
    if (mp->ma_keys == Py_EMPTY_KEYS) {
        return insert_to_emptydict(interp, mp, key, hash, value);
    }
    // 不是空字典,那么調(diào)用 insertdict
    return insertdict(interp, mp, key, hash, value);
}

所以最終會(huì)調(diào)用 insert_to_emptydict 或 insertdict,這里我們直接看 insertdict 函數(shù)的具體實(shí)現(xiàn)。

// Objects/dictobject.c
static int
insertdict(PyInterpreterState *interp, PyDictObject *mp,
           PyObject *key, Py_hash_t hash, PyObject *value)
{
    PyObject *old_value;
    // 如果 dk_kind 不等于 DICT_KEYS_GENERAL,即所有的 key 都是字符串
    // 但是新插入的 key 不是字符串,那么字典的結(jié)構(gòu)要發(fā)生改變
    // 此時(shí)會(huì)調(diào)用 insertion_resize 函數(shù),該函數(shù)內(nèi)部會(huì)調(diào)用 dictresize 函數(shù)
    // 關(guān)于 dictresize 后續(xù)介紹,這里暫時(shí)先不關(guān)注
    if (DK_IS_UNICODE(mp->ma_keys) && !PyUnicode_CheckExact(key)) {
        if (insertion_resize(interp, mp, 0) < 0)
            goto Fail;
        assert(mp->ma_keys->dk_kind == DICT_KEYS_GENERAL);
    }
    // 探測函數(shù),將 key 的哈希值映射成索引,該索引是哈希槽的索引
    // 然后返回該哈希槽存儲(chǔ)的鍵值對數(shù)組的索引,同時(shí)修改 old_value
    Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &old_value);
    if (ix == DKIX_ERROR)
        goto Fail;
    // GC 跟蹤
    MAINTAIN_TRACKING(mp, key, value);
    // 如果 ix == -1,說明 key 在字典中不存在
    if (ix == DKIX_EMPTY) {
        // 字典的版本號,無需關(guān)注
        uint64_t new_version = _PyDict_NotifyEvent(
                interp, PyDict_EVENT_ADDED, mp, key, value);
        // 對字典修改時(shí),dk_version 會(huì)重置為 0,無需關(guān)注
        mp->ma_keys->dk_version = 0;
        assert(old_value == NULL);
        // 如果鍵值對數(shù)組的長度小于等于 0,說明還沒有為鍵值對數(shù)組分配內(nèi)存
        // 那么依舊調(diào)用 insertion_resize,該函數(shù)后續(xù)解釋
        if (mp->ma_keys->dk_usable <= 0) {
            /* Need to resize. */
            if (insertion_resize(interp, mp, 1) < 0)
                goto Fail;
        }
        // 按照相同的規(guī)則對 key 的哈希值進(jìn)行映射,并返回哈希槽的索引
        // 如果沒有撞上 Dummy 態(tài)的哈希槽,那么 dk_indices[hashpos] 會(huì)等于 ix
        // 如果在映射的過程中,撞上了 Dummy 態(tài)的哈希槽,那么直接將該槽的索引返回
        // 但不管是哪一種情況,我們都找到了一個(gè)合法的槽
        Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, hash);
        // 新的 entry 會(huì)添加在鍵值對數(shù)組中索引為 mp->ma_keys->dk_nentries 的位置
        // 因?yàn)殒I值對始終是按照先來后到的順序追加的,然后調(diào)用 dictkeys_set_index
        // 將 entry 在鍵值對數(shù)組中的索引,賦值給 mp->ma_keys->dk_indices[hashpos]
        dictkeys_set_index(mp->ma_keys, hashpos, mp->ma_keys->dk_nentries);
        // 添加鍵值對,如果所有的 key 都是字符串
        if (DK_IS_UNICODE(mp->ma_keys)) {
            // 鍵值對的類型為 PyDictUnicodeEntry
            PyDictUnicodeEntry *ep;
            // dk_entries[dk_nentries] 便對應(yīng)新的 entry,由于內(nèi)存一開始便分配好了
            // 因此所謂添加,其實(shí)就是修改它的 me_key 和 me_value 字段
            // 將這兩個(gè)字段的值,修改為參數(shù) key 和參數(shù) value
            ep = &DK_UNICODE_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries];
            // 將 me_key 字段的值設(shè)置為參數(shù) key
            ep->me_key = key;
            // 如果 mp->ma_values 不為空,證明字典使用的是分離表
            if (mp->ma_values) {
                Py_ssize_t index = mp->ma_keys->dk_nentries;
                _PyDictValues_AddToInsertionOrder(mp->ma_values, index);
                assert (mp->ma_values->values[index] == NULL);
                // 分離表的話,value 統(tǒng)一由 mp->ma_values 維護(hù)
                // 至于 entry 里面的 me_value 字段則始終為 NULL
                mp->ma_values->values[index] = value;
            }
            // 否則說明字典使用的是結(jié)合表,將 entry->me_value 的值設(shè)置為 value
            else {
                ep->me_value = value;
            }
        }
        // 如果不滿足所有字段的值都是字符串,此時(shí)一定是結(jié)合表
        // 并且 entry 的類型是 PyDictKeyEntry
        else {
            PyDictKeyEntry *ep;
            // 獲取 entry,更新 me_key、me_value、me_hash
            ep = &DK_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries];
            ep->me_key = key;
            ep->me_hash = hash;
            ep->me_value = value;
        }
        // 字典長度加 1
        mp->ma_used++;
        // 更新字典的版本號
        mp->ma_version_tag = new_version;
        // 鍵值對數(shù)組還可以容納的 entry 個(gè)數(shù)減 1
        mp->ma_keys->dk_usable--;
        // 鍵值對已存儲(chǔ)的 entry 個(gè)數(shù)加 1
        mp->ma_keys->dk_nentries++;
        assert(mp->ma_keys->dk_usable >= 0);
        ASSERT_CONSISTENT(mp);
        return 0;
    }
    // 如果程序走到這里,說明 ix >= 0,即 key 已存在
    // 那么當(dāng) old_value != value 時(shí),要對值進(jìn)行更新
    if (old_value != value) {
        uint64_t new_version = _PyDict_NotifyEvent(
                interp, PyDict_EVENT_MODIFIED, mp, key, value);
        // 分離表,更新 mp->ma_values->values[ix]
        if (_PyDict_HasSplitTable(mp)) {
            mp->ma_values->values[ix] = value;
            if (old_value == NULL) {
                _PyDictValues_AddToInsertionOrder(mp->ma_values, ix);
                mp->ma_used++;
            }
        }
        else {
            // 結(jié)合表,獲取 entry,更新它的 me_value 字段
            assert(old_value != NULL);
            if (DK_IS_UNICODE(mp->ma_keys)) {
                DK_UNICODE_ENTRIES(mp->ma_keys)[ix].me_value = value;
            }
            else {
                DK_ENTRIES(mp->ma_keys)[ix].me_value = value;
            }
        }
        mp->ma_version_tag = new_version;
    }
    Py_XDECREF(old_value); 
    ASSERT_CONSISTENT(mp);
    Py_DECREF(key);
    return 0;

Fail:
    Py_DECREF(value);
    Py_DECREF(key);
    return -1;
}

以上就是獲取鍵值對,源碼細(xì)節(jié)和我們之前分析哈希表時(shí)說的是一樣的。

基于 key 獲取 value

如果是獲取 value,比如 v = d["a"],那么會(huì)調(diào)用 dict_as_mapping.mp_subscript,看一下它的具體邏輯。

// Objects/dictobject.c
static PyObject *
dict_subscript(PyDictObject *mp, PyObject *key)
{
    Py_ssize_t ix;
    Py_hash_t hash;
    PyObject *value;
    // 如果 key 不是字符串,或者 key 是字符串、但哈希值為 -1,那么計(jì)算哈希值
    if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) {
        hash = PyObject_Hash(key);
        if (hash == -1)
            return NULL;
    }
    // 探測函數(shù),將 key 映射成索引,并返回對應(yīng)的哈希槽存儲(chǔ)的鍵值對數(shù)組的索引
    // 并且在函數(shù)內(nèi)部,還會(huì)對參數(shù) value 進(jìn)行修改,所以這里要傳遞指針
    // 如果鍵值對存在,那么參數(shù) value 就是對應(yīng)的值,否則 value 會(huì)等于 NULL
    ix = _Py_dict_lookup(mp, key, hash, &value);
    if (ix == DKIX_ERROR)
        return NULL;
    // 當(dāng) ix == -1 或 value == NULL 時(shí),說明 key 對應(yīng)的鍵值對不存在
    if (ix == DKIX_EMPTY || value == NULL) {
        // 但如果 mp 不是字典,即 type(mp) is not dict
        // 那么說明 mp 的類型一定繼承了 dict
        if (!PyDict_CheckExact(mp)) {
            // 檢測 mp 是否定義了 __missing__ 方法,如果定義了則調(diào)用
            // 所以該方法要定義在繼承了 dict 的子類中
            PyObject *missing, *res;
            missing = _PyObject_LookupSpecial(
                    (PyObject *)mp, &_Py_ID(__missing__));
            if (missing != NULL) {
                res = PyObject_CallOneArg(missing, key);
                Py_DECREF(missing);
                return res;
            }
            else if (PyErr_Occurred())
                return NULL;
        }
        // 到這里說明 key 不存在,并且也沒有定義 __missing__,那么 KeyError
        _PyErr_SetKeyError(key);
        return NULL;
    }
    // 否則說明鍵值對存在,那么增加引用計(jì)數(shù),返回 value
    return Py_NewRef(value);
}

所以獲取 value 的話,也比較簡單,關(guān)鍵在于里面有一個(gè) __missing__ 方法,我們來解釋一下。

class Dict(dict):

    def __getitem__(self, item):
        return super().__getitem__(item)

    def __missing__(self, key):
        return f"不存在的 key:{key}"


d = Dict({"a": 1, "b": 2})
# 會(huì)執(zhí)行 Dict.__getitem__(d, "a")
# 在內(nèi)部會(huì)調(diào)用字典的 __getitem__
print(d["a"])  # 1
print(d["b"])  # 2

# 而在調(diào)用字典的 __getitem__ 時(shí),如果發(fā)現(xiàn) key 不存在
# 那么會(huì)嘗試尋找 __missing__ 方法
print(d["c"])  # 不存在的 key:c
print(d["高老師"])  # 不存在的 key:高老師

以上就是獲取鍵值對。

小結(jié)

關(guān)于字典是怎么創(chuàng)建的,以及它添加鍵值對、基于鍵獲取值的源碼細(xì)節(jié),我們就分析完了。當(dāng)然還沒有結(jié)束,字典還有很多的自定義方法,我們下一篇文章來剖析這些自定義方法的實(shí)現(xiàn)細(xì)節(jié)。

責(zé)任編輯:武曉燕 來源: 古明地覺的編程教室
相關(guān)推薦

2024-09-10 12:15:24

2024-08-26 11:13:26

字典entry自定義

2020-04-28 22:58:33

Tomcat架構(gòu)Service

2022-12-29 09:02:57

2024-08-08 11:05:22

2020-09-11 06:44:29

Python增強(qiáng)算術(shù)

2024-07-29 12:27:55

列表對象元素

2020-06-01 09:54:54

數(shù)據(jù)驅(qū)動(dòng)數(shù)字化CIO

2024-05-31 09:31:00

2010-03-24 13:04:12

Python嵌入

2024-08-29 12:37:11

2024-09-14 14:18:43

2022-02-28 08:17:24

重載函數(shù)JS前端

2024-11-05 15:02:41

2009-07-22 09:43:30

Scala類型

2021-08-30 22:38:47

VscodeMarkdown預(yù)覽

2024-04-18 08:51:10

原碼反碼補(bǔ)碼

2013-08-13 11:25:56

屏幕尺寸Android應(yīng)用

2022-09-29 09:17:47

進(jìn)程Linux創(chuàng)建

2009-02-17 18:52:06

網(wǎng)絡(luò)虛擬化路由系統(tǒng)數(shù)據(jù)中心
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號