Python源码之Set集合对象底层解析

1、PySetObject对象

  之前我们解析了Python中的dict对象,我们知道在dict的底层实际上是一个hash table,是一种映射关系。同样,集合对象底层也是hash table,因此,对于细节的描述在这一节就不细说了。关于hash table可参照这篇文章。python的dict对象底层实现,话不多说我们来看看Set对象在底层的定义。

#define PySet_MINSIZE 8

typedef struct {
   
	// 存储的元素的指针
    PyObject *key;
    // 缓存key的哈希值
    Py_hash_t hash;
} setentry;

typedef struct {
   
    PyObject_HEAD
    // active态和dummy态的entry的数量
    Py_ssize_t fill;
    // active态的entry的数量
    Py_ssize_t used;
    /* The table contains mask + 1 slots, and that's a power of 2.
     * We store the mask instead of the size because the mask is more
     * frequently needed.
     */
    Py_ssize_t mask;

    /* The table points to a fixed-size smalltable for small tables
     * or to additional malloc'ed memory for bigger tables.
     * The table pointer is never NULL which saves us from repeated
     * runtime null-tests.
     */
    setentry *table;
    Py_hash_t hash;             /* Only used by frozenset objects */
    Py_ssize_t finger;          /* Search finger for pop() */

    setentry smalltable[PySet_MINSIZE];
    PyObject *weakreflist;      /* List of weak references */
} PySetObject;

  我相信如果你阅读过Python2中的dict的源码,你就会发现诶怎么这个定义和python2中的dict定义那么像呢。上面一部分代码给出了注释,我来解释一下。

  • mask: 这个变量包含了mask + 1个slots,在dict中你已经明白了slots是干嘛的,它是2的幂次方,我们用mask来替代size是因为需要用来进行某种运算,这一点在dict的解析中我们可以看到,用于将哈希值映射到hash table对位置。
  • setentry *table:这是一个指向setentry数组的指针,分为两种情况。如果为small table,那么这个指针就指向这个固定大小的数组首地址,假如该set的元素数量被认定为是一个大的表,该指针就指向一个通过malloc分配的内存地址,总之它一定指向一个非NULL的内存。
  • Py_hash_t hash:此set对象的哈希值,仅用于frozenset对象
  • setentry smalltable[PySet_MINSIZE]:这就是我们上面提到的small table,它意味着当创建一个set对象时,如果容量低于PySet_MINSIZE时(这个宏被定义为8),就直接存放在这个数组里,如果大于8(实际上根据hash table的特性当存放的元素还没有8的时候),就会使用malloc在系统堆分配一块新的内存,让指针指向这块内存地址。这个机制在Python2的dict对象中也被采用。
    我们用一个图示来说明
    在这里插入图片描述

2、PySetObject对象的创建

  创建一个PySetObject对象是通过一个函数调用来实现的,这个函数的定义如下

setobject.c
PyObject *
PySet_New(PyObject *iterable)
{
   	
    return make_new_set(&PySet_Type, iterable);
}

static PyObject *
make_new_set(PyTypeObject *type, PyObject *iterable)
{
   
    PySetObject *so;
    so = (PySetObject *)type->tp_alloc(type, 0);
    if (so == NULL)
        return NULL;
    so->fill = 0;
    so->used = 0;
    so->mask = PySet_MINSIZE - 1;
    so->table = so->smalltable;
    so->hash = -1;
    so->finger = 0;
    so->weakreflist = NULL;
    if (iterable != NULL) {
   
        if (set_update_internal(so, iterable)) {
   
            Py_DECREF(so);
            return NULL;
        }
    }
    return (PyObject *)so;
}

  我们可以看到PySet_New()函数接受一个可迭代的对象参数,在内部又调用了make_new_set(),首先会为set的类型对象分配内存空间,如果内存分配失败则返回NULL,接着完成对成员变量的初始化。这里需要注意一点在对mask变量赋值的时候,令它的值等于size-1,这里是为了后面映射哈希表的位置设定,因为这个值会用来与哈希值做一个 “与” 的操作,如果与size做与运算,那么假如哈希值计算出来的二进制形式全部为1,那么映射出来的位置就是表长显然不存在这样的一个长度,所以需要减1. 默认将entry数组的指针table指向small table的内存区域。如果可迭代的对象不为NULL,则将元素一次放入到set对象中。

3、PySetObject对象元素的插入

  在元素的插入操作中,通过调用PySet_Add()函数来实现

setobject.c
int
PySet_Add(PyObject *anyset, PyObject *key)
{
   
	// 类型检查
    if (!PySet_Check(anyset) &&
        (!PyFrozenSet_Check(anyset) || Py_REFCNT(anyset) != 1)) {
   
        PyErr_BadInternalCall();
        return -1;
    }
    return set_add_key((PySetObject *)anyset, key);
}

set_add_key(PySetObject *so, PyObject *key)
{
   
    // 用于存储哈希值
    Py_hash_t hash;
    // 计算key的哈希值
    if (!PyUnicode_CheckExact(key) ||
        (hash = ((PyASCIIObject *) key)->hash) == -
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值