python3.7源码分析-集合(set)
python集合
set是⽆序且不重复的集合,是可变的,通常⽤来从列表中删除重复项以及计算数学运算,如交集、并集、差分和对称差分等集合操作。set ⽀持 x in set, len(set),和 for x in set。作为⼀个⽆序的集合,set不记录元素位置或者插⼊点。因此,sets不⽀持 indexing, 或其它类序列的操作。
python集合概述
在set中,对应的set的值的存储是通过结构setentry来保存数据值的;
typedef struct {
PyObject *key;
Py_hash_t hash;            /* Cached hash code of the key */
} setentry;
key就是保存的数据,hash就是保存的数据的hash,便于查,set也是基于hash表来实现。对应的sete
ntry所对应的set的数据结构如下;
typedef struct {
PyObject_HEAD
Py_ssize_t fill;            /* Number active and dummy entries*/    // 包括已经使⽤的entry与空entry值的总和
Py_ssize_t used;            /* Number active entries */              // 已经使⽤可⽤的总量
/* 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;                                // 与hash求和的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];                                // 保存数据的数组 默认初始化为8个元素,通过table指向
PyObject *weakreflist;      /* List of weak references */
} PySetObject;
⼀个set就对应⼀个PySetObject类型数据,set会根据保存的元素⾃动调整⼤⼩。相关的内存布局如下;
python集合(set)⽰例
⽰例脚本如下:
set_a = {1,2} 
set_a.add(3)
set_a.add(4)
ve(1)
set_a.update({3,})
set_a.union({1,5})
通过python反汇编获取该脚本的字节码;python -m dis set_test.py
输出的字节码如下所⽰;
1          0 LOAD_CONST              0 (1)
3 LOAD_CONST              1 (2)
6 BUILD_SET                2
9 STORE_NAME              0 (set_a)
2          12 LOAD_NAME                0 (set_a)
15 LOAD_ATTR                1 (add)
18 LOAD_CONST              2 (3)
21 CALL_FUNCTION            1
24 POP_TOP
3          25 LOAD_NAME                0 (set_a)
28 LOAD_ATTR                1 (add)
31 LOAD_CONST              3 (4)
34 CALL_FUNCTION            1
37 POP_TOP
4          38 LOAD_NAME                0 (set_a)
41 LOAD_ATTR                2 (remove)
44 LOAD_CONST              0 (1)
47 CALL_FUNCTION            1
50 POP_TOP
5          51 LOAD_NAME                0 (set_a)
54 LOAD_ATTR                3 (update)
57 LOAD_CONST              2 (3)
60 BUILD_SET                1
63 CALL_FUNCTION            1
66 POP_TOP
6          6
7 LOAD_NAME                0 (set_a)
70 LOAD_ATTR                4 (union)
73 LOAD_CONST              0 (1)
76 LOAD_CONST              4 (5)
79 BUILD_SET                2
82 CALL_FUNCTION            1
85 POP_TOP
86 LOAD_CONST              5 (None)
89 RETURN_VALUE
通过该字节码指令可知,创建set调⽤了BUILD_SET指令,初始化完成之后,就调⽤set的add⽅法添
加元素,调⽤remove删除元素,调⽤update来更新集合,通过union来合并集合。接下来就详细分析⼀下相关的操作流程。
set的创建与初始化
查BUILD_SET的虚拟机执⾏函数如下;
// Python/ceval.c
TARGET(BUILD_SET) {
PyObject *set = PySet_New(NULL);            // 新建并初始化⼀个set
int err = 0;
int i;
if (set == NULL)
goto error;
for (i = oparg; i > 0; i--) {                // 将传⼊初始化的参数传⼊
PyObject *item = PEEK(i);
if (err == 0)
err = PySet_Add(set, item);          // 并依次对set进⾏添加操作
Py_DECREF(item);
}
STACKADJ(-oparg);                  // 移动弹栈
if (err != 0) {
Py_DECREF(set);
goto error;
}
PUSH(set);                     // 讲set压栈
DISPATCH();                    // 执⾏下⼀条指令
}
此时继续查看PySet_New函数的执⾏流程;
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;                                          // 初始化的时候都为0
so->used = 0;
so->mask = PySet_MINSIZE - 1;                          // PySet_MINSIZE默认我8,mask为7    so->table = so->smalltable;                            // 将保存数据的头指针指向table
so->hash = -1;                                          // 设置hash值为-1
so->finger = 0;
so->weakreflist = NULL;
if (iterable != NULL) {                                // 如果有迭代器
if (set_update_internal(so, iterable)) {            // 将内容更新到so中
Py_DECREF(so);
return NULL;
}
}
return (PyObject *)so;                                  // 返回初始化完成的set
}
从PySet_New的执⾏流程可知,字典的初始化过程就是初始化相关数据结构。set的插⼊
在本例的初始化过程中,由于传⼊了初始值1,2,所以会在执⾏字节码指令的时候,执⾏PySet_Add,该函数的本质与set_a.add(3)本质都调⽤了更底层set_add_key函数;
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);  // 向字典中添加key;
python虚拟机
}
继续查看set_add_key函数的执⾏过程;
static int
set_add_key(PySetObject *so, PyObject *key)
{
Py_hash_t hash;
if (!PyUnicode_CheckExact(key) ||
(hash = ((PyASCIIObject *) key)->hash) == -1) {
hash = PyObject_Hash(key);                  // 获取传⼊值的hash值
if (hash == -1)                            // 如果不能hash则返回-1
return -1;
}
return set_add_entry(so, key, hash);            // 计算完成后添加值
}
该函数主要就是检查传⼊的key是否能够被hash,如果能够被hash则直接返回,如果能被hash则继续调⽤set_add_entry函数将值加⼊到set中;
static int
set_add_entry(PySetObject *so, PyObject *key, Py_hash_t hash)
{
setentry *table;
setentry *freeslot;
setentry *entry;
size_t perturb;
size_t mask;
size_t i;                      /* Unsigned for defined overflow behavior */
size_t j;
int cmp;
/* Pre-increment is necessary to prevent arbitrary code in the rich
comparison from deallocating the key just before the insertion. */
Py_INCREF(key);                                                            // 提⾼key的引⽤计数
restart:
mask = so->mask;                                                          // 获取so->mask
i = (size_t)hash & mask;                                                  // 通过传⼊的hash与mask求索引下标
entry = &so->table[i];   // 获取索引对应的值
if (entry->key == NULL)                                                    // 如果获取索引的值没有被使⽤则直接跳转到found_unused处执⾏
goto found_unused;
freeslot = NULL;
perturb = hash;                                                          // perturb设置为当前hash值
while (1) {
if (entry->hash == hash) {                                              // 如果当前hash值相等

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。