說明
- python源碼版本:3.8.3
參考:
python源碼剖析
https://yq.aliyun.com/users/1709307684254463
int
在源碼實現中,python3
之前用int
和long
兩種類型來表示數字,而python3
開始則統一使用long
來表示數字(盡管在使用python時,顯示的是int
,實際上在底層是long
類型)
int定義
struct _longobject {
// python變長對象頭
PyObject_VAR_HEAD
// digit定義:typedef uint32_t digit;
// ob_digit是一個單一元素,內容為uint型,并且是可變大小的數組(單一元素的數組放在一個struct的尾端,則數組大小是可變的),存放的是具體的數值
digit ob_digit[1];
};
從int的定義看出存放int具體數值的數據類型實際上是一個數組,因此python中int的大小沒有限制,而數組里存放的數組都是無符號數字,那么正負數如何區分呢?實際上其將判斷正負數、零的標識存放在了頭部PyObject_VAR_HEAD
的ob_size
中,而ob_digit
中只存儲其絕對值數值,后面從int對象的創建邏輯當中可以看到這些
創建int對象
int對象的創建方式會根據傳入的數值或者類型轉換等情況來決定,例如:小于C中long類型大小的數值,一般基于PyLong_FromLong
方法創建,而大于該數的則可能基于PyLong_FromString
方法創建,這里簡單介紹下通過long
方式創建的邏輯:
// 通過數值創建python中int對象方法,例如:a = 100
PyObject *
PyLong_FromLong(long ival)
{
PyLongObject *v;
// abs_ival中存放絕對值數值
unsigned long abs_ival;
unsigned long t; /* unsigned so >> doesn't propagate sign bit */
int ndigits = 0;
// 數值標記:正數(1),負數(-1),零(0)
int sign;
// 檢查如果是小整數,則直接返回緩沖池的小整數對象,后面會介紹,在python3.8中緩沖池范圍是[-5, 257)
CHECK_SMALL_INT(ival);
// 將數值和正負數的標識分開
if (ival < 0) {
abs_ival = 0U-(unsigned long)ival;
sign = -1;
}
else {
abs_ival = (unsigned long)ival;
sign = ival == 0 ? 0 : 1;
}
// 對于小于PyLong_SHIFT位的數進入快速通道,PyLong_SHIFT的值基于當前系統中定義指針的大小確定
// (指針在32位中size為4,64位中為8,因此例如32位平臺下32768以下的數字都可以進入該通道)
if (!(abs_ival >> PyLong_SHIFT)) {
// 創建一個數值指向區域size為1*sizeof(digit)的long對象
v = _PyLong_New(1);
if (v) {
// 可以看出ob_size用來標識是正數、負數還是0
Py_SIZE(v) = sign;
// ob_digit[0]里存入的是無符號的數值
v->ob_digit[0] = Py_SAFE_DOWNCAST(
abs_ival, unsigned long, digit);
}
return (PyObject*)v;
}
// 對于指針大小為4字節的情況(例如32位系統),申請對象空間的方式
#if PyLong_SHIFT==15
/* 2 digits */
if (!(abs_ival >> 2*PyLong_SHIFT)) {
v = _PyLong_New(2);
if (v) {
Py_SIZE(v) = 2*sign;
v->ob_digit[0] = Py_SAFE_DOWNCAST(
abs_ival & PyLong_MASK, unsigned long, digit);
v->ob_digit[1] = Py_SAFE_DOWNCAST(
abs_ival >> PyLong_SHIFT, unsigned long, digit);
}
return (PyObject*)v;
}
#endif
// 對于較大的數值,需要獲取位數,然后申請對應大小的long對象
t = abs_ival;
// 獲取數值所需的位數
while (t) {
++ndigits;
t >>= PyLong_SHIFT;
}
// 后面的邏輯跟之前的創建方式基本一樣
v = _PyLong_New(ndigits);
if (v != NULL) {
digit *p = v->ob_digit;
Py_SIZE(v) = ndigits*sign;
t = abs_ival;
while (t) {
*p++ = Py_SAFE_DOWNCAST(
t & PyLong_MASK, unsigned long, digit);
t >>= PyLong_SHIFT;
}
}
return (PyObject *)v;
}
小整數緩沖池
在python中對于int數據,其定義了緩沖池,對常用范圍的小整數都進行了緩存處理,從而提高數值使用的效率,緩沖池部分源碼如下:
// 定義小整數的緩沖池
// #define NSMALLPOSINTS 257
// #define NSMALLNEGINTS 5
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
// 獲取小整數對象
static PyObject *
get_small_int(sdigit ival)
{
PyObject *v;
assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
// 在符合小整數的范圍內,返回直接緩沖池中的對象
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);
...
return v;
}
// 如果是小整數,則直接從小整數緩沖池當中獲取對象
#define CHECK_SMALL_INT(ival) \
do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
return get_small_int((sdigit)ival); \
} while(0)
可以看出在[-5, 257)
之間是存在緩存的,證明如下:
>>> id(-5)
1665722640
>>> id(-5)
1665722640
# -5的id都一樣
>>> id(-6)
2331088351536
>>> id(-6)
2331088351312
# -6的id發生了變化
>>> id(256)
1665730992
>>> id(256)
1665730992
# 256的id都一樣
>>> id(257)
2331088351312
>>> id(257)
2331088351536
# 257的id發生了變化
因此對于小整數使用較多的場景,我們可以通過修改小整數的緩沖池范圍來進行優化
加法邏輯
由于python中的int屬于不可變類型數據,因此在進行加法運算時,會創建一個新的int對象來存儲計算的結果值,并且由于int中數據的存儲是基于數據,因此在計算時實際上模擬了從低位開始逐位相加運算的方式來進行運算,而通過這種存儲和計算方式,一般也就不用擔心大數計算時的溢出等問題了,源碼邏輯如下:
// 加法運算邏輯
static PyLongObject *
x_add(PyLongObject *a, PyLongObject *b)
{
Py_ssize_t size_a = Py_ABS(Py_SIZE(a)), size_b = Py_ABS(Py_SIZE(b));
PyLongObject *z;
Py_ssize_t i;
digit carry = 0;
// 如果a比b小,就交換兩個數,以保證a是大的那個數
if (size_a < size_b) {
{ PyLongObject *temp = a; a = b; b = temp; }
{ Py_ssize_t size_temp = size_a;
size_a = size_b;
size_b = size_temp; }
}
// 以最大的那個值的size+1為新創建的long對象大小
z = _PyLong_New(size_a+1);
if (z == NULL)
return NULL;
// 低位開始逐位相加(兩個數中小的那個數的最高位及以下位相加)
for (i = 0; i < size_b; ++i) {
carry += a->ob_digit[i] + b->ob_digit[i];
z->ob_digit[i] = carry & PyLong_MASK;
carry >>= PyLong_SHIFT;
}
// 高位只剩下a的值,直接將a的值填充
for (; i < size_a; ++i) {
carry += a->ob_digit[i];
z->ob_digit[i] = carry & PyLong_MASK;
carry >>= PyLong_SHIFT;
}
// 新創建的對象保存對應結果
z->ob_digit[i] = carry;
// long_normalize將數值前面的0刪除(并沒有真正刪除釋放,而是改變其允許指向數值的范圍大小)
return long_normalize(z);
}
list
結構定義
typedef struct {
// python變長對象的頭部分,其中list的真實數據的長度就存在頭部的ob_size里
PyObject_VAR_HEAD
// 存放所有數據的數組,每個數據都是PyObject類型
PyObject **ob_item;
// list申請的內存大小
Py_ssize_t allocated;
} PyListObject;
緩沖池
// #define PyList_MAXFREELIST 80
// list緩沖池空間定義,可以看出大小是80
static PyListObject *free_list[PyList_MAXFREELIST];
// 緩沖池中已使用的數量
static int numfree = 0;
當創建了一個list對象后,將會存入緩沖區,如果這個list對象沒有被使用,將會留在緩沖區,等到下一次創建list對象時,如果緩沖區里存在list對象,則直接返回該對象
創建list對象
例如對于a = [1,2,3]
的字節碼指令如下:
0 LOAD_CONST 1 (1)
2 LOAD_CONST 2 (2)
4 LOAD_CONST 3 (3)
6 BUILD_LIST 3
8 STORE_FAST 0 (a)
前三個指令依次將1、2、3壓入棧中,然后通過BUILD_LIST
創建一個list
并從棧中取出的數據存入,其中build_list
的操作源碼如下:
// BUILD_LIST指令操作
case TARGET(BUILD_LIST): {
// 創建一個空的list對象(里面沒有存放數據,但是已經分配了對應大小的內存,oparg就是list需要的長度)
PyObject *list = PyList_New(oparg);
if (list == NULL)
goto error;
while (--oparg >= 0) {
// 依次從棧中取出list需要存入的元素(對應上面的指令就是依次取出3、2、1),然后存入到list的指令位置當中
PyObject *item = POP();
PyList_SET_ITEM(list, oparg, item);
}
PUSH(list);
DISPATCH();
}
其中通過PyList_New
函數創建一個list
對象,源碼如下:
PyObject *
PyList_New(Py_ssize_t size)
{
PyListObject *op;
if (size < 0) {
PyErr_BadInternalCall();
return NULL;
}
// 如果緩沖池里存在list對象,則取出
if (numfree) {
// 緩沖池list對象數量-1
numfree--;
// 獲取緩沖池的對應list對象
op = free_list[numfree];
_Py_NewReference((PyObject *)op);
} else {
// 否則創建一個list空間大小的對象
op = PyObject_GC_New(PyListObject, &PyList_Type);
if (op == NULL)
return NULL;
}
// 如果list里沒有內容,則ob_item(實際存放list內容的地方)設置為空
if (size <= 0)
op->ob_item = NULL;
// list中存在內容,則申請對應大小的空間給ob_item
else {
op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *));
if (op->ob_item == NULL) {
Py_DECREF(op);
return PyErr_NoMemory();
}
}
// 設置list數據長度
Py_SIZE(op) = size;
// 設置list容量大小,可以看出list初始化時的分配的容量大小跟數據長度是一樣的
op->allocated = size;
_PyObject_GC_TRACK(op);
return (PyObject *) op;
}
我們可以進行測試:
>>> id([1,2,3])
7002632
>>> id([1,2,3,4])
7002632
# 可以看出都是復用緩沖池的list對象
>>> id([1,2,3,4,5])
7002632
>>> a = [1,2,3]
>>> id(a)
7002632
# 將對象賦值指向后
>>> id([1,2,3])
8095848
# 可以看出創建了一個新的list對象
append邏輯
static int
app1(PyListObject *self, PyObject *v)
{
// 獲取當前list尺寸
Py_ssize_t n = PyList_GET_SIZE(self);
assert (v != NULL);
if (n == PY_SSIZE_T_MAX) {
PyErr_SetString(PyExc_OverflowError,
"cannot add more objects to list");
return -1;
}
// 更新list尺寸,使list能夠存入n+1數量的數據,如果失敗則返回-1
if (list_resize(self, n+1) < 0)
return -1;
Py_INCREF(v);
// 設置第n個位置的值為v
PyList_SET_ITEM(self, n, v);
return 0;
}
int
PyList_Append(PyObject *op, PyObject *newitem)
{
if (PyList_Check(op) && (newitem != NULL))
return app1((PyListObject *)op, newitem);
PyErr_BadInternalCall();
return -1;
}
extend主邏輯
...
// 插入數據是否可迭代判斷
iterable = PySequence_Fast(iterable, "argument must be iterable");
if (!iterable)
return NULL;
// 計算要插入的數據長度
n = PySequence_Fast_GET_SIZE(iterable);
if (n == 0) {
/* short circuit when iterable is empty */
Py_DECREF(iterable);
Py_RETURN_NONE;
}
// 獲取list當前長度
m = Py_SIZE(self);
/* It should not be possible to allocate a list large enough to cause
an overflow on any relevant platform */
assert(m < PY_SSIZE_T_MAX - n);
// 當前list長度+插入長度判斷,只申請一次
if (list_resize(self, m + n) < 0) {
Py_DECREF(iterable);
return NULL;
}
/* note that we may still have self == iterable here for the
* situation a.extend(a), but the following code works
* in that case too. Just make sure to resize self
* before calling PySequence_Fast_ITEMS.
*/
/* populate the end of self with iterable's items */
src = PySequence_Fast_ITEMS(iterable);
dest = self->ob_item + m;
// 遍歷設置值
for (i = 0; i < n; i++) {
PyObject *o = src[i];
Py_INCREF(o);
dest[i] = o;
}
Py_DECREF(iterable);
Py_RETURN_NONE;
...
insert邏輯
static int
ins1(PyListObject *self, Py_ssize_t where, PyObject *v)
{
Py_ssize_t i, n = Py_SIZE(self);
PyObject **items;
if (v == NULL) {
PyErr_BadInternalCall();
return -1;
}
if (n == PY_SSIZE_T_MAX) {
PyErr_SetString(PyExc_OverflowError,
"cannot add more objects to list");
return -1;
}
// 尺寸判斷
if (list_resize(self, n+1) < 0)
return -1;
if (where < 0) {
where += n;
// 負值索引如果加上長度還<0,則置為0
if (where < 0)
where = 0;
}
// 索引大于最大長度,則設置為往后面添加
if (where > n)
where = n;
items = self->ob_item;
// insert位置后面的數據都后挪一位
for (i = n; --i >= where; )
items[i+1] = items[i];
Py_INCREF(v);
// 插入數據
items[where] = v;
return 0;
}
append/extend/insert效率問題
通過前面三種添加方式的源碼實現可以看出:insert
的邏輯中,需要對插入部分后面的數據進行后移操作,以及對插入位置進行一些范圍處理,所以效率上會比append
差一些;在append
里每添加一條數據都需要進行尺寸判斷,不夠則重新申請,而extend
則是一口氣算出最終需要的尺寸,只需要申請一次即可,所以效率上append
會比extend
差一些,特別是在添加的數據量越大時越明顯,所以在數據量大的情況下三者的效率應該是:insert
<append
<extend
(如果數據特別少,那么因為append
邏輯最簡單,所以效率應該是extend
<insert
<append
),如下代碼可以驗證:
from time import time
l = 10000000
data = [i for i in range(l)]
def count_time(func):
def wrapper():
s = time()
func()
print(func.__name__, ":", time() - s)
wrapper()
@count_time
def insert():
li = []
for i in range(l):
li.insert(i, data[i])
@count_time
def append():
li = []
for i in range(l):
li.append(data[i])
@count_time
def extend():
li = []
li.extend(data)
# insert : 2.52603816986084
# append : 1.7159614562988281
# extend : 0.21300077438354492
而如果我們用Python去實現可變序列(MutableSequence
)相關的類型時,由于Python無法進行像C那樣底層的操作,所以extend
的實現實際上就是遍歷append
而已,此時append
和extend
的效率就沒有多大的差別了,源碼如下:
def extend(self, values):
'S.extend(iterable) -- extend sequence by appending elements from the iterable'
for v in values:
self.append(v)
list尺寸更新邏輯
static int
list_resize(PyListObject *self, Py_ssize_t newsize)
{
PyObject **items;
size_t new_allocated, num_allocated_bytes;
Py_ssize_t allocated = self->allocated;
// 如果數據量在總大小的一半以上,且小于總大小,則無需重新計算尺寸
if (allocated >= newsize && newsize >= (allocated >> 1)) {
assert(self->ob_item != NULL || newsize == 0);
Py_SIZE(self) = newsize;
return 0;
}
// 新內存大小分配規則
new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6);
// 最大長度限制
if (new_allocated > (size_t)PY_SSIZE_T_MAX / sizeof(PyObject *)) {
PyErr_NoMemory();
return -1;
}
if (newsize == 0)
new_allocated = 0;
// 內存大小計算
num_allocated_bytes = new_allocated * sizeof(PyObject *);
// 內存申請
items = (PyObject **)PyMem_Realloc(self->ob_item, num_allocated_bytes);
if (items == NULL) {
PyErr_NoMemory();
return -1;
}
// list存放數據的地方
self->ob_item = items;
// list中存放的數據長度
Py_SIZE(self) = newsize;
// list內存分配的長度
self->allocated = new_allocated;
return 0;
}
remove邏輯
static PyObject *
list_remove(PyListObject *self, PyObject *value)
/*[clinic end generated code: output=f087e1951a5e30d1 input=2dc2ba5bb2fb1f82]*/
{
Py_ssize_t i;
// 遍歷所有值,找到第一個要刪除的,將其刪除,然后返回
for (i = 0; i < Py_SIZE(self); i++) {
PyObject *obj = self->ob_item[i];
// 引用+1
Py_INCREF(obj);
// PyObject_RichCompareBool會根據第三個參數來進行比較,這里比較的是是否相等
// 相等則返回1,不相等返回0,還有相關判斷會返回-1
int cmp = PyObject_RichCompareBool(obj, value, Py_EQ);
// 操作完畢,引用-1
Py_DECREF(obj);
if (cmp > 0) {
// 刪除第i個操作,該方法邏輯較多,其中刪除的邏輯可以參考動態數組的刪除方式
if (list_ass_slice(self, i, i+1,
(PyObject *)NULL) == 0)
Py_RETURN_NONE;
// Py_RETURN_NONE源碼:
// #define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
// 將None引用+1,并返回None
return NULL;
}
// cmp小于0屬于其他情況
else if (cmp < 0)
return NULL;
}
// 遍歷完都沒有找到要刪除的,則會拋出異常
PyErr_SetString(PyExc_ValueError, "list.remove(x): x not in list");
return NULL;
}
包含邏輯(x in list
)
static int
list_contains(PyListObject *a, PyObject *el)
{
PyObject *item;
Py_ssize_t i;
int cmp;
// 遍歷取出內容進行比較,當找到時停止遍歷
for (i = 0, cmp = 0 ; cmp == 0 && i < Py_SIZE(a); ++i) {
item = PyList_GET_ITEM(a, i);
Py_INCREF(item);
cmp = PyObject_RichCompareBool(el, item, Py_EQ);
Py_DECREF(item);
}
return cmp;
}
索引邏輯(index
)
static PyObject *
list_index_impl(PyListObject *self, PyObject *value, Py_ssize_t start,
Py_ssize_t stop)
/*[clinic end generated code: output=ec51b88787e4e481 input=40ec5826303a0eb1]*/
{
Py_ssize_t i;
// 負值索引(如:list[-1])的實質就是`list[len(list) + (-1)]`
if (start < 0) {
start += Py_SIZE(self);
if (start < 0)
start = 0;
}
// stop會自動調整到>=0
if (stop < 0) {
stop += Py_SIZE(self);
if (stop < 0)
stop = 0;
}
// 遍歷查找
for (i = start; i < stop && i < Py_SIZE(self); i++) {
PyObject *obj = self->ob_item[i];
Py_INCREF(obj);
int cmp = PyObject_RichCompareBool(obj, value, Py_EQ);
Py_DECREF(obj);
// 找到了則將索引轉成long類型返回
if (cmp > 0)
return PyLong_FromSsize_t(i);
else if (cmp < 0)
return NULL;
}
PyErr_Format(PyExc_ValueError, "%R is not in list", value);
return NULL;
}
修改邏輯(li[i] = x
)
int
PyList_SetItem(PyObject *op, Py_ssize_t i,
PyObject *newitem)
{
PyObject **p;
// list類型檢查
if (!PyList_Check(op)) {
Py_XDECREF(newitem);
PyErr_BadInternalCall();
return -1;
}
// 范圍檢查
if (!valid_index(i, Py_SIZE(op))) {
Py_XDECREF(newitem);
PyErr_SetString(PyExc_IndexError,
"list assignment index out of range");
return -1;
}
// 找到對應位置進行修改操作
p = ((PyListObject *)op) -> ob_item + i;
Py_XSETREF(*p, newitem);
// Py_XSETREF源碼:
// #define Py_XSETREF(op, op2) \
// do { \
// PyObject *_py_tmp = _PyObject_CAST(op); \
// (op) = (op2); \
// Py_XDECREF(_py_tmp); \
// } while (0)
// 解釋:將舊的內容替換成新的內容,并將舊的內容引用計數-1
return 0;
}
dict
python中大部分的運行機制當中都采用了dict來進行數據的管理,所以為了最高效的查詢性能,dict底層選擇了基于哈希表實現。其中在python3.6之前,dict的實現里將數據存在了hash表里,所以之前的dict是無序且十分消耗內存的,而在python3.6以后,官方對dict進行了很大地改進(主要是將數據和hash部分分離),包括從無序變為有序,在一定程度上減少了內存的消耗,以及部分操作性能的提升等,具體參考:https://zhuanlan.zhihu.com/p/73426505
dict定義
在python3.6之前,字典的數組中的對應位置都存放著完整的鍵值對信息-(hash, key, value)
,這種字典類型被稱為combined
型,后來為了提升性能,將完整的鍵值對信息存放到另一個數組entries
當中,而對于原來字典的數組里,對應位置只存放了鍵值對在entries
中的索引,這種字典類型被稱為split
型,這里主要對split
型字典進行介紹,官方提供的內存布局如下:
+---------------+
| dk_refcnt | key集合的引用數
| dk_size | key集合的大小
| dk_lookup | 字典查詢方法
| dk_usable | 字典中key集合可用大小
| dk_nentries | 字典對應的entries集合的內容數量
+---------------+
| dk_indices | key集合(哈希表部分),存放所有key在entries中對應的索引
| |
+---------------+
| dk_entries | 存放所有的鍵值對數據信息
| |
+---------------+
基于上面的布局,源碼中對于dict
的結構定義如下:
// dict總體結構
typedef struct {
// 通用對象頭
PyObject_HEAD
// 鍵值對的數量
Py_ssize_t ma_used;
// 版本標記
uint64_t ma_version_tag;
// key集合,哈希表就存在這里面
PyDictKeysObject *ma_keys;
// entries集合,split中,所有的鍵值對都存在ma_values里,而combined型的ma_values將會是null
PyObject **ma_values;
} PyDictObject;
// key集合結構
typedef struct _dictkeysobject PyDictKeysObject;
struct _dictkeysobject {
// key集合的引用數
Py_ssize_t dk_refcnt;
// 哈希表索引數組的大小
Py_ssize_t dk_size;
// 字典查詢方法
dict_lookup_func dk_lookup;
// 字典中key集合可用大小
Py_ssize_t dk_usable;
// entries中鍵值對的數量
Py_ssize_t dk_nentries;
// 每個鍵對應存放數據位置的索引
char dk_indices[];
};
// entries集合中單個元素的結構
typedef struct {
Py_hash_t me_hash;
PyObject *me_key;
// 在split型中,value存放在ma_values中,所以只有combined型的時候,me_value才存放value
PyObject *me_value;
} PyDictKeyEntry;
dict的幾種狀態
在python3.6之前,dict中的鍵值對主要有以下三種狀態:
- Unused:此時key為null,即還未使用當前位置,此時索引值被設為
-1
- Active:如果當前位置設置了鍵值對,那么就會進入該狀態,表示正在使用中,此時索引值就是所在位置的索引
- Dummy:當被使用的key刪除時,將會進入該狀態,用于避免探測鏈斷開而導致索引出錯的問題,此時索引值為
-2
在python3.6以后,由于索引和鍵值對分開存儲,于是便取消了Dummy
態,而使用Pending
態來進行代替:
- Pending:同樣是刪除鍵值對以后進入的狀態,此時索引不變,但是對應索引存儲的value將會被刪除,從而等待新的對應索引的key插入
幾種狀態的源碼定義如下:
#define DKIX_EMPTY (-1)
#define DKIX_DUMMY (-2)
#define DKIX_ERROR (-3)
dict基本配置
// 哈希表的起始尺寸為8
#define PyDict_MINSIZE 8
// 哈希表相對于索引數量的尺寸
#define ESTIMATE_SIZE(n) (((n)*3+1) >> 1)
// 當需要更新dict尺寸時,尺寸大小為索引數*3
#define GROWTH_RATE(d) ((d)->ma_used*3)
dict緩沖池
// dict緩沖池大小,可以看出尺寸和list緩沖池一樣是80
#ifndef PyDict_MAXFREELIST
#define PyDict_MAXFREELIST 80
#endif
// 可以發現對dict對象和dict中的key集合對象都有進行緩沖池操作
static PyDictObject *free_list[PyDict_MAXFREELIST];
static int numfree = 0;
static PyDictKeysObject *keys_free_list[PyDict_MAXFREELIST];
static int numfreekeys = 0;
創建dict
// 空字典的key集合
static PyDictKeysObject empty_keys_struct = {
1, /* dk_refcnt */
1, /* dk_size */
lookdict_split, /* dk_lookup */
0, /* dk_usable (immutable) */
0, /* dk_nentries */
// 可以看到初始化的哈希表里都是Unused狀態
// #define DKIX_EMPTY (-1)
{DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY,
DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY}, /* dk_indices */
};
// 空的entries
static PyObject *empty_values[1] = { NULL };
// 空的key集合
#define Py_EMPTY_KEYS &empty_keys_struct
// dict對象創建入口
PyObject *
PyDict_New(void)
{
// 引用+1
dictkeys_incref(Py_EMPTY_KEYS);
// 通過空集合和空entries創建一個空字典
return new_dict(Py_EMPTY_KEYS, empty_values);
}
// 創建一個dict對象
static PyObject *
new_dict(PyDictKeysObject *keys, PyObject **values)
{
PyDictObject *mp;
assert(keys != NULL);
// 緩存池操作,和list的差不多
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) {
dictkeys_decref(keys);
if (values != empty_values) {
free_values(values);
}
return NULL;
}
}
// 初始化操作,設置keys、values等內容,注意這里的keys和values的內容還都是空的
mp->ma_keys = keys;
mp->ma_values = values;
mp->ma_used = 0;
mp->ma_version_tag = DICT_NEXT_VERSION();
ASSERT_CONSISTENT(mp);
return (PyObject *)mp;
}
dict添加
// 添加/設置邏輯
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;
// 獲取對象的哈希值,如果不是可哈希的,則無法執行setitem操作
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);
}
// 添加或修改操作函數
return insertdict(mp, key, hash, value);
}
// dict添加和更新的主邏輯
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;
}
// 尋找對應key的索引
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);
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;
}
// 如果對應的key的映射為空,則代表添加數據
if (ix == DKIX_EMPTY) {
assert(old_value == NULL);
if (mp->ma_keys->dk_usable <= 0) {
if (insertion_resize(mp) < 0)
goto Fail;
}
// 尋找哈希表中當前key的插入位置
Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, hash);
// ep指向entries的最后一位(索引為dk_nentries處)
ep = &DK_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries];
// 設置indices中對應entries的索引,可以看到設置的entries索引就是entries的數量,因為鍵值對每次都是往entries的最后一位添加的
dictkeys_set_index(mp->ma_keys, hashpos, mp->ma_keys->dk_nentries);
// 設置新加入entries的鍵值對數據
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;
}
// 如果不是添加操作,并且指定entries的value和當前傳入的value不同,則需要更新value
if (old_value != value) {
// split型表的操作
if (_PyDict_HasSplitTable(mp)) {
// 直接更新entries中對應索引的value
mp->ma_values[ix] = value;
// 當前位置屬于pending狀態(已刪除狀態),則說明之前沒有使用,現在使用了,所以使用數+1
if (old_value == NULL) {
assert(ix == mp->ma_used);
mp->ma_used++;
}
}
// combined型表的操作
else {
assert(old_value != NULL);
// 由于key、value都是存在哈希表中,直接修改哈希表對應位置的value
DK_ENTRIES(mp->ma_keys)[ix].me_value = value;
}
mp->ma_version_tag = DICT_NEXT_VERSION();
}
Py_XDECREF(old_value);
ASSERT_CONSISTENT(mp);
Py_DECREF(key);
return 0;
Fail:
Py_DECREF(value);
Py_DECREF(key);
return -1;
}
// 查找hash值在哈希表中的映射
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 = dictkeys_get_index(keys, i);
// 哈希沖突處理
for (size_t perturb = hash; ix >= 0;) {
perturb >>= PERTURB_SHIFT;
i = (i*5 + perturb + 1) & mask;
ix = dictkeys_get_index(keys, i);
}
return i;
}
dict查詢
源碼邏輯如下:
// 查詢函數主邏輯
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);
// key不為字符串時,直接調用lookdict函數查詢
if (!PyUnicode_CheckExact(key)) {
mp->ma_keys->dk_lookup = lookdict;
return lookdict(mp, key, hash, value_addr);
}
// 字符串的索引情況,邏輯上基本和lookdict函數相同(分析在下面),但是簡化了一些操作
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 = dictkeys_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();
}
// 字典非字符串查詢的邏輯實現
static Py_ssize_t _Py_HOT_FUNCTION
lookdict(PyDictObject *mp, PyObject *key,
Py_hash_t hash, PyObject **value_addr)
{
size_t i, mask, perturb;
PyDictKeysObject *dk;
PyDictKeyEntry *ep0;
top:
// 字典對應的所有key
dk = mp->ma_keys;
// 字典對應的所有結果
ep0 = DK_ENTRIES(dk);
// 防止哈希越界的mask(類似對哈希取余作用),DK_MASK定義如下:
// #define DK_MASK(dk) (((dk)->dk_size)-1)
mask = DK_MASK(dk);
// 對應的哈希值
perturb = hash;
// 哈希映射到字典的位置
i = (size_t)hash & mask;
for (;;) {
// 獲取映射位置存放的索引(即在entries中的索引)
Py_ssize_t ix = dictkeys_get_index(dk, i);
// 索引位置存儲索引值不存在的情況
if (ix == DKIX_EMPTY) {
// 設置返回的value為空
*value_addr = NULL;
return ix;
}
// 索引位置對應的entry索引存在
if (ix >= 0) {
// 到entry集中取出對應位置的entry結果
PyDictKeyEntry *ep = &ep0[ix];
assert(ep->me_key != NULL);
// 如果entry中的key和查詢的key相同,說明是要查找的數據
if (ep->me_key == key) {
// 設置對應的返回value
*value_addr = ep->me_value;
return ix;
}
// 如果key不同哈希卻相同的情況
if (ep->me_hash == hash) {
PyObject *startkey = ep->me_key;
Py_INCREF(startkey);
// 比較兩個key是否相等,1代表相等,0代表不等,-1代表比較失敗
int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);
Py_DECREF(startkey);
// 比較失敗的情況,說明是異常情況,返回一個錯誤狀態
if (cmp < 0) {
*value_addr = NULL;
return DKIX_ERROR;
}
if (dk == mp->ma_keys && ep->me_key == startkey) {
// 比較相等
if (cmp > 0) {
*value_addr = ep->me_value;
return ix;
}
}
// 如果能到這里(哈希相同,key卻不同),說明字典在使用期間發生了改變,因此需要重新查詢
else {
/* The dict was mutated, restart */
goto top;
}
}
}
// 不是對應的key,則通過下面探測函數繼續尋找下一個位置
perturb >>= PERTURB_SHIFT;
i = (i*5 + perturb + 1) & mask;
}
Py_UNREACHABLE();
}
// 獲取對應key的索引中存放的entry索引(具體內容存放的地方)
static inline Py_ssize_t
dictkeys_get_index(PyDictKeysObject *keys, Py_ssize_t i)
{
// 字典大小
Py_ssize_t s = DK_SIZE(keys);
Py_ssize_t ix;
// 基于字典大小,選擇對應的數據類型存放索引值
if (s <= 0xff) {
// 獲取到存放所有key的地方
int8_t *indices = (int8_t*)(keys->dk_indices);
// 獲取當前key對應的索引
ix = indices[i];
}
else if (s <= 0xffff) {
// 基本和前面一樣,只是存放索引的類型不同
int16_t *indices = (int16_t*)(keys->dk_indices);
ix = indices[i];
}
#if SIZEOF_VOID_P > 4
else if (s > 0xffffffff) {
int64_t *indices = (int64_t*)(keys->dk_indices);
ix = indices[i];
}
#endif
else {
int32_t *indices = (int32_t*)(keys->dk_indices);
ix = indices[i];
}
// 結果必須是索引存儲的值存在或者為空
assert(ix >= DKIX_DUMMY);
return ix;
}
dict刪除
dict的刪除操作并不會真正將entries中對應的索引刪除,而是進行一種偽刪除操作-將entries中對應位置的內容清空(否則需要對后面的數據進行前挪操作,并且還要更新后面幾個數據的key,效率上的問題可想而知...),源碼邏輯如下:
// 刪除操作邏輯
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);
// 使用數-1
mp->ma_used--;
mp->ma_version_tag = DICT_NEXT_VERSION();
ep = &DK_ENTRIES(mp->ma_keys)[ix];
// 設置對應位置為dummy態
dictkeys_set_index(mp->ma_keys, hashpos, DKIX_DUMMY);
// 因為存在dummy態,所以需要修改索引方式為支持dummy態的
ENSURE_ALLOWS_DELETIONS(mp);
old_key = ep->me_key;
// 設置entries中對應位置的key和value為空
ep->me_key = NULL;
ep->me_value = NULL;
Py_DECREF(old_key);
Py_DECREF(old_value);
ASSERT_CONSISTENT(mp);
return 0;
}
// 刪除主邏輯
int
PyDict_DelItem(PyObject *op, PyObject *key)
{
Py_hash_t hash;
assert(key);
// 可hash校驗
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);
}
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;
// 找到對應的entries索引
ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &old_value);
// 異常情況處理
if (ix == DKIX_ERROR)
return -1;
// 如果索引的entry位置內容為空,則報錯
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);
}
update主邏輯
static int
dict_merge(PyObject *a, PyObject *b, int override)
{
PyDictObject *mp, *other;
Py_ssize_t i, n;
PyDictKeyEntry *entry, *ep0;
assert(0 <= override && override <= 2);
if (a == NULL || !PyDict_Check(a) || b == NULL) {
PyErr_BadInternalCall();
return -1;
}
// 左邊的轉字典類型
mp = (PyDictObject*)a;
// 如果右邊的是字典類型,且可以用字典的方法迭代
if (PyDict_Check(b) && (Py_TYPE(b)->tp_iter == (getiterfunc)dict_iter)) {
// 右邊的也轉字典類型
other = (PyDictObject*)b;
// 如果兩個字典是同一個或者右邊的字典為空,則直接返回
if (other == mp || other->ma_used == 0)
return 0;
// 如果左邊的字典為空,到時候就直接將右邊的內容覆蓋到左邊的字典里
if (mp->ma_used == 0)
override = 1;
// 如果左邊字典允許使用的數量比右邊已使用的key數量小,則進行擴容操作
if (USABLE_FRACTION(mp->ma_keys->dk_size) < other->ma_used) {
if (dictresize(mp, ESTIMATE_SIZE(mp->ma_used + other->ma_used))) {
return -1;
}
}
// 獲取entries數組進行迭代
ep0 = DK_ENTRIES(other->ma_keys);
for (i = 0, n = other->ma_keys->dk_nentries; i < n; i++) {
PyObject *key, *value;
Py_hash_t hash;
entry = &ep0[i];
key = entry->me_key;
hash = entry->me_hash;
// split/combined型獲取value方式兼容
if (other->ma_values)
value = other->ma_values[i];
else
value = entry->me_value;
if (value != NULL) {
int err = 0;
Py_INCREF(key);
Py_INCREF(value);
// 如果覆蓋模式的話,則直接將右邊字典的key設置進去(如果左邊字典有該key,則會被覆蓋)
if (override == 1)
err = insertdict(mp, key, hash, value);
// 如果在左邊的字典里沒找到對應的key,則插入數據
else if (_PyDict_GetItem_KnownHash(a, key, hash) == NULL) {
if (PyErr_Occurred()) {
Py_DECREF(value);
Py_DECREF(key);
return -1;
}
err = insertdict(mp, key, hash, value);
}
// 如果左邊的字典里有該key,且override為2,則不允許覆蓋,報錯
else if (override != 0) {
_PyErr_SetKeyError(key);
Py_DECREF(value);
Py_DECREF(key);
return -1;
}
// 所以如果override==0,那么如果左邊有該key,則不進行任何操作
Py_DECREF(value);
Py_DECREF(key);
if (err != 0)
return -1;
// 字典update期間被修改導致的entries發生改變
if (n != other->ma_keys->dk_nentries) {
PyErr_SetString(PyExc_RuntimeError,
"dict mutated during update");
return -1;
}
}
}
}
// 左邊不為字典類型的情況
else {
PyObject *keys = PyMapping_Keys(b);
PyObject *iter;
PyObject *key, *value;
int status;
if (keys == NULL)
return -1;
// 獲取key的迭代器
iter = PyObject_GetIter(keys);
Py_DECREF(keys);
if (iter == NULL)
return -1;
// 迭代key進行賦值操作
for (key = PyIter_Next(iter); key; key = PyIter_Next(iter)) {
// 非覆蓋的情況
if (override != 1) {
// 如果左邊的字典里存在該key
if (PyDict_GetItemWithError(a, key) != NULL) {
// 如果override==2,則報錯
if (override != 0) {
_PyErr_SetKeyError(key);
Py_DECREF(key);
Py_DECREF(iter);
return -1;
}
// 如果override==0,則直接跳到下一個key
Py_DECREF(key);
continue;
}
// 當線程當中存在異常
else if (PyErr_Occurred()) {
Py_DECREF(key);
Py_DECREF(iter);
return -1;
}
}
// 如果覆蓋模式,則直接設置對應的鍵值對
value = PyObject_GetItem(b, key);
if (value == NULL) {
Py_DECREF(iter);
Py_DECREF(key);
return -1;
}
status = PyDict_SetItem(a, key, value);
Py_DECREF(key);
Py_DECREF(value);
if (status < 0) {
Py_DECREF(iter);
return -1;
}
}
Py_DECREF(iter);
if (PyErr_Occurred())
return -1;
}
// 對combined/split型字典的相關檢查
ASSERT_CONSISTENT(a);
return 0;
}
// update字典
int
PyDict_Update(PyObject *a, PyObject *b)
{
// update字典主邏輯,依次傳入2個字典,第三個參數代表是否覆蓋(0-不覆蓋,1-覆蓋,2-有相同key則報錯)
return dict_merge(a, b, 1);
}
迭代器
迭代原理
通用迭代器的本質(有些特殊對象會根據自身迭代特點,實現特定格式的迭代器,這里主要介紹最常用的迭代器)就是在內部定義了一個索引,代表迭代到的位置,然后再將一個指針指向要迭代的序列(同時對序列引用+1),當進行next
遍歷時則取出當前序列對應索引位置的值,并將索引+1,源碼如下:
// 迭代器定義
typedef struct {
PyObject_HEAD
// 迭代到的位置索引
Py_ssize_t it_index;
// 指向迭代的序列
PyObject *it_seq; /* Set to NULL when iterator is exhausted */
} seqiterobject;
// 創建一個迭代器
PyObject *
PySeqIter_New(PyObject *seq)
{
seqiterobject *it;
if (!PySequence_Check(seq)) {
PyErr_BadInternalCall();
return NULL;
}
it = PyObject_GC_New(seqiterobject, &PySeqIter_Type);
if (it == NULL)
return NULL;
// 初始化索引為0
it->it_index = 0;
// 對指向的序列引用+1
Py_INCREF(seq);
// 指向要迭代的序列
it->it_seq = seq;
_PyObject_GC_TRACK(it);
return (PyObject *)it;
}
// next邏輯
static PyObject *
iter_iternext(PyObject *iterator)
{
seqiterobject *it;
PyObject *seq;
PyObject *result;
assert(PySeqIter_Check(iterator));
it = (seqiterobject *)iterator;
seq = it->it_seq;
if (seq == NULL)
return NULL;
if (it->it_index == PY_SSIZE_T_MAX) {
PyErr_SetString(PyExc_OverflowError,
"iter index too large");
return NULL;
}
// 取出序列中對應索引的值
result = PySequence_GetItem(seq, it->it_index);
if (result != NULL) {
// 索引+1
it->it_index++;
return result;
}
if (PyErr_ExceptionMatches(PyExc_IndexError) ||
PyErr_ExceptionMatches(PyExc_StopIteration))
{
PyErr_Clear();
it->it_seq = NULL;
Py_DECREF(seq);
}
return NULL;
}
例如list轉迭代器時,會將list的引用計數+1,并在內部維護一個變量存放當前索引的位置,當對list迭代完成時,會將list的引用計數-1,從而釋放list,舉例:
class MyList(list):
def __del__(self):
print("list被回收...")
l = MyList([1,2,3])
# 迭代器使用到了list,因此list的引用+1
it = iter(l)
# list的引用-1
del l
# 迭代list
for i in it:
print(i)
# list迭代完成,list引用-1
print("end...")
# 可以看到list的回收在end之前,即迭代器完成了對list的全部迭代以后,就會釋放list,即使后面還會用到這個迭代器
print(it)
# 1
# 2
# 3
# list被回收...
# end...
# <list_iterator object at 0x000002530C65DE80>
迭代器其他接口
python中為通用迭代器提供了一些額外的接口,如:獲取迭代器的長度、修改當前索引到的位置,源碼如下:
// 獲取迭代器長度
static PyObject *
iter_len(seqiterobject *it, PyObject *Py_UNUSED(ignored))
{
Py_ssize_t seqsize, len;
if (it->it_seq) {
if (_PyObject_HasLen(it->it_seq)) {
// 獲取指向的序列長度
seqsize = PySequence_Size(it->it_seq);
if (seqsize == -1)
return NULL;
}
else {
Py_RETURN_NOTIMPLEMENTED;
}
// 迭代器的長度就是序列的長度減去當前迭代的位置索引
len = seqsize - it->it_index;
if (len >= 0)
return PyLong_FromSsize_t(len);
}
return PyLong_FromLong(0);
}
// 修改索引位置
static PyObject *
iter_setstate(seqiterobject *it, PyObject *state)
{
Py_ssize_t index = PyLong_AsSsize_t(state);
if (index == -1 && PyErr_Occurred())
return NULL;
if (it->it_seq != NULL) {
if (index < 0)
index = 0;
it->it_index = index;
}
Py_RETURN_NONE;
}
// 對外接口定義
static PyMethodDef seqiter_methods[] = {
{"__length_hint__", (PyCFunction)iter_len, METH_NOARGS, length_hint_doc},
{"__reduce__", (PyCFunction)iter_reduce, METH_NOARGS, reduce_doc},
{"__setstate__", (PyCFunction)iter_setstate, METH_O, setstate_doc},
{NULL, NULL} /* sentinel */
};
- 獲取迭代器長度示例:
a = [1,2,3]
b = iter(a)
# 獲取迭代器的剩余長度
print(b.__length_hint__())
next(b)
print(b.__length_hint__())
# 3
# 2
- 修改索引位置示例:
a = [1,2,3]
b = iter(a)
print(next(b))
# 迭代索引重新設為0
b.__setstate__(0)
print(next(b))
# 1
# 1
待更新...