【通过Cpython3.9源码看看字典到底是咋回事】

news2024/11/18 14:34:45

在这里插入图片描述

字典结构

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

上述代码定义了 CPython 中的 PyDictObject 结构体,表示 Python 字典对象。

PyDictObject 结构体包含以下几个成员变量:

  1. PyObject_HEAD:Python 对象头部,包括对象类型、引用计数等信息。
  2. ma_used:字典中当前存储的键值对数量。
  3. ma_version_tag:字典的版本标记,每次字典被修改都会改变。
  4. ma_keys:指向 PyDictKeysObject 结构体的指针,表示字典中的键。
  5. ma_values:指向 PyObject* 类型的指针,表示字典中的值。

如果 ma_values 为 NULL,表示该字典采用“combined”方式存储,即键和值都存储在 ma_keys 中;如果 ma_values 不为 NULL,则采用“splitted”方式存储,即键存储在 ma_keys 中,值存储在 ma_values 中。

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

       - lookdict(): general-purpose, and may return DKIX_ERROR if (and
         only if) a comparison raises an exception.

       - lookdict_unicode(): specialized to Unicode string keys, comparison of
         which can never raise an exception; that function can never return
         DKIX_ERROR.

       - lookdict_unicode_nodummy(): similar to lookdict_unicode() but further
         specialized 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;

    /* 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, SIZEOF_VOID_P is minimum. */
    char dk_indices[];  /* char is required to avoid strict aliasing. */

    /* "PyDictKeyEntry dk_entries[dk_usable];" array follows:
       see the DK_ENTRIES() macro */
};

上述代码定义了 CPython 中的 _dictkeysobject 结构体,表示 Python 字典对象中的键的数据结构。

_dictkeysobject 结构体包含以下几个成员变量:

  1. dk_refcnt:键对象的引用计数。
  2. dk_size:哈希表的大小,必须是 2 的幂次方。
  3. dk_lookup:指向查找哈希表中元素的函数指针。
  4. dk_usable:哈希表中可用的条目数。
  5. dk_nentries:哈希表中已经使用的条目数。
  6. dk_indices:哈希表,保存了索引,或者是 DKIX_EMPTY(-1),或者是 DKIX_DUMMY(-2)。索引值必须满足 0 <= indice < USABLE_FRACTION(dk_size),其中 USABLE_FRACTION(dk_size) 是可用的索引数量,即 dk_usable。索引的大小取决于 dk_size 的大小,当 dk_size <= 0xff 时,索引为 char* 类型,当 dk_size <= 0xffff 时,索引为 int16_t* 类型,当 dk_size <= 0xffffffff 时,索引为 int32_t* 类型,否则为 int64_t* 类型。
  7. dk_entries:保存键对象的数组,dk_usabledk_nentries 决定了数组的长度,具体的长度由 DK_ENTRIES() 宏计算得到。

总之,_dictkeysobject 结构体定义了 Python 字典对象中键的基本属性和结构。

字典元素查找

1280116143@qq.com
PyObject *
PyDict_GetItem(PyObject *op, PyObject *key)
{
    Py_hash_t hash;
    Py_ssize_t ix;
    PyDictObject *mp = (PyDictObject *)op;
    PyThreadState *tstate;
    PyObject *value;

    if (!PyDict_Check(op))
        return NULL;
    if (!PyUnicode_CheckExact(key) ||
        (hash = ((PyASCIIObject *) key)->hash) == -1)
    {
        hash = PyObject_Hash(key);
        if (hash == -1) {
            PyErr_Clear();
            return NULL;
        }
    }

    /* We can arrive here with a NULL tstate during initialization: try
       running "python -Wi" for an example related to string interning.
       Let's just hope that no exception occurs then...  This must be
       _PyThreadState_GET() and not PyThreadState_Get() because the latter
       abort Python if tstate is NULL. */
    tstate = _PyThreadState_GET();
    if (tstate != NULL && tstate->curexc_type != NULL) {
        /* preserve the existing exception */
        PyObject *err_type, *err_value, *err_tb;
        PyErr_Fetch(&err_type, &err_value, &err_tb);
        ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &value);
        /* ignore errors */
        PyErr_Restore(err_type, err_value, err_tb);
        if (ix < 0)
            return NULL;
    }
    else {
        ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &value);
        if (ix < 0) {
            PyErr_Clear();
            return NULL;
        }
    }
    return value;
}

PyDict_GetItem函数是用于从字典中获取一个键对应的值的函数。其函数定义如下:

PyObject *PyDict_GetItem(PyObject *op, PyObject *key)
  • op: 要从中获取键值对的字典对象。
  • key: 要获取值的键对象。

该函数首先会检查 op 是否为字典对象,如果不是则返回 NULL。接着会检查 key 是否为 Unicode 字符串对象,如果不是则需要对其进行哈希运算。如果哈希运算失败则会清除错误并返回 NULL

然后该函数调用字典对象的 dk_lookup 函数进行查找。如果查找失败则会根据情况清除错误并返回 NULL。如果查找成功则返回相应的值对象。dk_lookup_dictkeysobject 结构体中的一个函数指针,它指向一个用于在哈希表中查找元素的函数。这个函数会接收三个参数:指向哈希表的 _dictkeysobject 指针,要查找的元素 key,和元素 key 的哈希值 hash。如果找到了元素,则把元素的值存储在指向 value 的指针中,并返回该元素在哈希表中的索引;如果没找到元素,则返回 -1。在不同的情况下,dk_lookup 可以指向不同的查找函数,例如 lookdict()lookdict_unicode() 等。

需要注意的是,如果函数调用过程中发生了异常,该函数会尝试保留异常信息,查找完成后再恢复之前的异常。

字典新增元素

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

    if (mp->ma_keys == Py_EMPTY_KEYS) {
        return insert_to_emptydict(mp, key, hash, value);
    }
    /* insertdict() handles any resizing that might be necessary */
    return insertdict(mp, key, hash, value);
}

PyDict_SetItem是向字典中设置一个键值对的函数,其参数包括一个指向待操作字典的指针 op,一个键 key 和一个值 value

首先,PyDict_SetItem检查 op 是否是一个字典,如果不是,则返回错误。

接着,key 被哈希并存储在变量 hash 中,如果 key 不是 Unicode 对象或者 hash 值为 -1,则调用 PyObject_Hashkey 进行哈希。如果 PyObject_Hash 失败,则返回错误。

然后,如果字典为空(即不存在键值对),则调用 insert_to_emptydict 将键值对添加到字典中。如果字典非空,则调用 insertdict 函数将键值对添加到字典中,并处理可能需要的重新哈希和重新分配内存等操作。最后,函数返回一个整数,表示操作是否成功。

// Same to insertdict but specialized for ma_keys = Py_EMPTY_KEYS.
static int
insert_to_emptydict(PyDictObject *mp, PyObject *key, Py_hash_t hash,
                    PyObject *value)
{
    assert(mp->ma_keys == Py_EMPTY_KEYS);

    PyDictKeysObject *newkeys = new_keys_object(PyDict_MINSIZE);
    if (newkeys == NULL) {
        return -1;
    }
    if (!PyUnicode_CheckExact(key)) {
        newkeys->dk_lookup = lookdict;
    }
    dictkeys_decref(Py_EMPTY_KEYS);
    mp->ma_keys = newkeys;
    mp->ma_values = NULL;

    Py_INCREF(key);
    Py_INCREF(value);
    MAINTAIN_TRACKING(mp, key, value);

    size_t hashpos = (size_t)hash & (PyDict_MINSIZE-1);
    PyDictKeyEntry *ep = DK_ENTRIES(mp->ma_keys);
    dictkeys_set_index(mp->ma_keys, hashpos, 0);
    ep->me_key = key;
    ep->me_hash = hash;
    ep->me_value = value;
    mp->ma_used++;
    mp->ma_version_tag = DICT_NEXT_VERSION();
    mp->ma_keys->dk_usable--;
    mp->ma_keys->dk_nentries++;
    return 0;
}

insert_to_emptydict 函数是用于将一个键值对添加到一个空的字典中的函数。它会创建一个新的 PyDictKeysObject 对象,指向的数组大小为 PyDict_MINSIZE,并将指定的键值对存储在该数组中的第一个位置。函数返回0表示成功,返回-1表示失败。在添加键值对之前,函数会增加键和值的引用计数,并在字典中记录这些引用,以便可以在之后跟踪这些对象。如果函数成功,它会更新字典的计数器和版本标记。如果出现任何错误,它会清理之前增加的引用计数并返回 -1。

/*
Internal routine to insert a new item into the table.
Used both by the internal resize routine and by the public insert routine.
Returns -1 if an error occurred, or 0 on success.
*/
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];
        dictkeys_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_CONSISTENT(mp);
        return 0;
    }

    if (old_value != value) {
        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_CONSISTENT(mp);
    Py_DECREF(key);
    return 0;

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

这是 Python 字典对象中用于插入新条目的内部函数 insertdict 的实现代码。它用于将新的键值对插入到字典中。该函数有三个参数:字典对象 mp,键 key 和值 value

在插入之前,我们需要确保 key 和 value 的引用计数都增加了,以防止它们被误释放。如果字典对象拥有拆分表,且键不是 Unicode 字符串,就需要进行插入大小调整,调用 insertion_resize 函数。这是因为拆分表的索引只是包含字符集合的子集,所以添加元素时可能会出现哈希冲突,导致拆分表无法正确解决冲突。

接下来,调用字典键查找函数 mp->ma_keys->dk_lookup 来查找键在字典中的位置,如果找到,则将新值赋给原位置的值。如果键在字典中没有找到,就将新键值对插入到字典中。如果字典对象没有拆分表,则将新键值对放入 me_value 字段中。如果字典对象拥有拆分表,则将值放入 ma_values 中相应的索引中。

如果没有找到空闲位置,则需要进行扩容。我们需要为新条目找到一个空闲的哈希槽,并将该槽的索引插入到 dk_indices 数组中。新条目的指针存储在键入表中的相应索引处,存储新值。

最后,我们需要在引用计数计数中减少旧值的引用计数,以防止内存泄漏,将键的引用计数减少,以使字典对象正确工作,并返回成功或失败的结果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/419334.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

更灵敏、更精准、更智能,机器人避障也能“随心所欲”

对于穿梭在人群中的机器人们而言&#xff0c;避障的重要性不言而喻。 随着机器人应用越来越广泛&#xff0c;机器人的作业场景的复杂度在显著增加&#xff0c;与人类的接触也越加密集&#xff0c;这对机器人的避障功能提出了新要求&#xff0c;不仅要更加精准灵敏&#xff0c;还…

js对象赋值

js对象赋值1. 含有相同的键进行赋值2. 复制对象&#xff08;遍历赋值&#xff09;3. 复制对象&#xff08;直接赋值&#xff09;4. es6的Object.assign()方法拷贝对象 &#xff08;深拷贝&#xff0c;但没完全深&#xff09;5. JSON.parse(JSON.stringify(obj))7. 总结&#xf…

深度学习 - 39. EGES 与推荐系统用户冷启动

目录 一.引言 二.EGES 算法演进 1.淘宝推荐系统简介 2.BGE、GES、EGES 简介 2.1 用户行为序列构建 2.2 BGE / Base Graph Embedding 2.3 GES / Graph Embedding with Side Information 2.4 EGES / Enhanced Graph Embedding with Side Information 三.模型试验 1.离…

TryHackMe-Ra 2(Windows渗透测试)

Ra 2 WindCorp最近发生了安全漏洞。从那以后&#xff0c;他们加强了基础设施&#xff0c;从错误中吸取教训。但也许还不够&#xff1f;您已经设法进入了他们的本地网络… 端口扫描 循例 nmap 域名跟Ra前部基本一样, 多了个selfservice SMB枚举 smbmap enum4linux也没什么信…

CLIP:语言-图像表示之间的桥梁

最近GPT4的火爆覆盖了一个新闻&#xff1a;midjourney v5发布&#xff0c;DALLE2&#xff0c;midjourney都可以从文本中生成图像&#xff0c;这种模型要求人工智能同时理解语言和图像数据。 传统的基于人工智能的模型很难同时理解语言和图像。因为自然语言处理和计算机视觉一直…

每日一练——Day 13

前言&#xff1a; 小亭子正在努力的学习编程&#xff0c;接下来将开启编程题的练习~~ 分享的文章都是学习的笔记和感悟&#xff0c;如有不妥之处希望大佬们批评指正~~ 同时如果本文对你有帮助的话&#xff0c;烦请点赞关注支持一波, 感激不尽~~ 第一题 题目描述&#xff1a; 刷…

一篇文章搞懂Docker、DockerCompose

文章目录1、初识Docker1.1、项目部署的问题1.2、Docker如何解决依赖的兼容问题的&#xff1f;1.3.Docker解决操作系统环境差异1.4、Docker如何解决不同系统环境的问题&#xff1f;1.5、小总结2、Docker 和虚拟机的区别3、Docker架构3.1、镜像和容器3.2、DockerHub3.3、Docker架…

2023年最强手机远程控制横测:ToDesk、向日葵、Airdroid三款APP免Root版本

前言 随着远程办公和远程协作的日益普及&#xff0c;跨设备、系统互通的远程控制软件已经成为职场人士不可或缺的工具之一。在国内&#xff0c;向日葵和ToDesk是最著名的远程控制软件&#xff1b;而在国外&#xff0c;则有微软远程桌面、AirDroid、TeamViewer、AnyDesk、Parse…

【移动端】fiddler配置及使用

文章目录安装配置桌面端配置移动端配置Android 手机上的配置苹果手机上的配置使用视图功能区域栏图标说明相关问题来源Fiddler是以代理web服务器的形式工作的&#xff0c;它使用代理地址:127.0.0.1&#xff0c;端口:8888。当Fiddler退出的时候它会自动注销&#xff0c;这样就不…

linux 消息队列 msgget/msgsnd/msgrecv

专栏内容&#xff1a;linux下并发编程个人主页&#xff1a;我的主页座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物&#xff0e;目录 前言 概述 原理 消息队列的大小 查看资源 接口 代码演示 结尾 前言 本专栏主要…

Git记录

Git日常命令 版本管理 git tag 删除本地tag git tag --delete v4.2.1推送本地标签 git push origin v4.2.1创建本地标签 git tag v4.2.1 分支管理 远程仓库地址管理 远程地址添加 意外着我可以将一个项目添加多个远程的仓库地址&#xff0c;只不过需要注意的一点就是git …

mysql数据表操作

1、alter 修改表名 : alter table 旧表名 rename as 新表名添加字段 : alter table 表名 add字段名 列属性[属性]修改字段 : alter table 表名 modify 字段名 列类型[属性]alter table 表名 change 旧字段名 新字段名 列属性[属性]删除字段 : alter table 表名 drop 字段名2、…

多目标柔性生产作业车间——反世代距离(IGD)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录反世代距离IGD概念收敛性能以及分布性能举例计算反世代距离IGD概念 反世代距离是一个综合性能的评价指标&#xff0c;主要是应用至求解多目标问题中&#xff0c;评价…

Java企业级开发学习笔记(2.3)利用MyBatis实现关联查询

该文章主要为完成实训任务&#xff0c;详细实现过程及结果见【http://t.csdn.cn/ZVEZd】 文章目录一、创建数据库表1.1 创建教师表1.2 创建班级表1.3 创建学生表二、创建于数据库表对应的实体类2.1 创建教师实体类2.2 创建学生实体类2.3 创建班级实体类三、创建班级映射器配置文…

Leetcode394 字符串解码 递归和非递归

字符串解码 https://leetcode.cn/problems/decode-string/ 给定一个经过编码的字符串&#xff0c;返回它解码后的字符串。 编码规则为: k[encoded_string]&#xff0c;表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 你可以认为输入字符串总是有效…

你搞清楚了吗?| GET请求方式的长度限制到底是多少?

目录 &#x1f4cd; 浏览器限制 &#x1f4cd; 服务器限制 在大多数人的一贯认识中&#xff0c;一直认为get请求方式有2048B的长度限制&#xff0c;其实这种说法是有失偏颇的&#xff0c;甚至可以说是错误的。 这个问题一直以来似乎是被N多人误解&#xff0c;其实Http Get方…

2.3-3单链表的查找

按位查找&#xff1a; so easy (1)边界情况 i0 没有循环&#xff0c;直接返回头节点 &#xff08;2&#xff09;如果i8 当不合法&#xff0c;返回NULL. (3)普通情况i3;(平均时间复杂度为O&#xff08;n&#xff09;) 进行封装&#xff1a;避免重复&#xff0c;更加简洁更…

CRYSTALS-Dilithium

文章目录简介1和2版本区别2和3的区别1.介绍1.1基本方法概述密钥生成算法签名过程验证1.2Dilithium实现注意事项安全性。基础操作环操作模约简。元素的大小NTT域表示2.3 HashingsignatureGenζ ← {0,1}256\{{0, 1\}}^{256}{0,1}256(ρ, ρ, K) ∈ {0,1}256\{{0, 1\}}^{256}{0,1…

【springBoot篇2】springBoot日志篇

目录 一、日志有什么作用 作用1&#xff1a;快速定位问题的所在之处(最主要) 作用2&#xff1a;记录用户的登录日志 作用3&#xff1a;记录系统的操作日志 作用4&#xff1a;记录方法的执行时间 二、日志怎样使用 ①先得到日志对象(slf4j的Logger对象) ​​​编辑 ②根…

Python 无监督学习实用指南:1~5

原文&#xff1a;Hands-on unsupervised learning with Python 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 本文来自【ApacheCN 深度学习 译文集】&#xff0c;采用译后编辑&#xff08;MTPE&#xff09;流程来尽可能提升效率。 不要担心自己的形象&#xff0c;只关…