若是對象的引用數量增長,就在該對象的計數器上進行增量操做。在實際中它是由宏Py_INCREF() 執行的。編程
#define Py_INCREF(op) (((PyObject*)(op))->ob_refcnt++) #define Py_XINCREF(op) if ((op) == NULL) ; else Py_INCREF(op)
除了增量操做外,還要執行NULL檢查,Py_XINCREF(op)。函數
Include/object.hdebug
typedef ssize_t Py_ssize_t;
ssize_t型,在32位環境下是int在64位下是long,它的大小由系統決定。這裏定義的計數器它是能夠爲負數的,那麼就有問題了,計數器是有符號整數,他能表達的最大數僅僅是無符號整數的一半。這樣不會內存溢出嗎?咱們以前說對象是4字節對齊的,既然是按4字節對齊,咱們就能夠獲得這樣分下來,即便全部對象都指向某一個對象,也是不會溢出的。設計
那麼,負的計數器表達的是什麼?在debug中,會存在減數操做過分,和增量操做遺失的狀況。負的計數器就是爲它而設計的。在debug中,Py NegativeRefcount() 函數會把變爲負數的對象信息當成錯誤信息輸出。指針
其中成員 tp_dealloc 存着負責釋放各個對象的函數指針,好比下面這個釋放元組對象的函數指針。code
Objects/tupleobject.c對象
static void tupledealloc(register PyTupleObject *op) { register Py_ssize_t i; register Py_ssize_t len = Py_SIZE(op); if (len > 0) { i = len; /* 將元組內的元素進行減量 */ while (--i >= 0) Py_XDECREF(op->ob_item[i]); } /* 釋放元組對象 */ Py_TYPE(op)->tp_free((PyObject *)op); Py_TRASHCAN_SAFE_END(op) }
PyObject_GC_Del()blog
void PyObject_GC_Del(void *op) { PyGC_Head *g = AS_GC(op); /* 省略部分:釋放前的處理 */ PyObject_FREE(g); }
這裏的 PyObject_FREE(),就是上一節中的 PyObject_Free()函數,這個函數會對對象進行釋放。不過我是怎麼知道的呢。此處又有宏定義。#define PyObject_FREE PyObject_Free。位於Include/objimpl.h內存
元組減量操做以下圖示:
就是咱們類裏常常寫的 __del__
終結器指的是與對象的釋放處理掛鉤的一個功能。列表和字典等內置對象基本上是不能設置終結器的,能定義終結器的只有用戶建立的類。
# 一個終結器 class Foo(object): def __def__(self): # 定義終結器 print("GKD")
這種狀況下,當Foo被釋放的時候,就會輸出GKD。
那麼Foo實例其實是怎麼調用的呢?以下示:
Objects/typeobject.c:subtype_dealloc():單獨拿出終結器的部分
static void subtype_dealloc(PyObject *self) { PyTypeObject *type, *base; destructor basedealloc; type = Py_TYPE(self); if (type->tp_del) { _PyObject_GC_TRACK(self); type->tp_del(self); } /* 省略 */ }
實例的狀況下,變量 tp_del 中保存着執行終結器所需的 slot_tp_del() 函數
Objects/typeobject.c:slot_tp_del()
static void slot_tp_del(PyObject *self) { static PyObject *del_str = NULL; PyObject *del, *res; self->ob_refcnt = 1; /* 若是有__del__就執行它 */ del = lookup_maybe(self, "__del__", &del_str); if (del != NULL) { res = PyEval_CallObject(del, NULL); /* 省略部分:錯誤檢查和後處理等 */ } if (--self->ob_refcnt == 0) return; /* 退出函數 */ /* 省略部分:最終化時有引用的狀況下的應對處理 */ }
先用lookup_maybe(),取出實例中的__del__,而後使用 PyEval_CallObject()來執行它。
在python中,正常狀況是要對對象的計數器進行增量和減量操做的。可是並非全部地方都須要這樣作。
好比說在python中編寫c的擴展模塊:當從局部變量引用某個對象,大多數狀況下是能夠不執行計數處理的,由於從局部來講,咱們引用它以後給計數器增量,退出後局部後又要減量。這實際上沒有任何意義。不過也能夠這樣作。
原本計數器的做用是告訴GC這個對象被引用了,不要回收。那若是計數器的值已是大於0了。咱們還須要這樣的增量計數器嗎?增量以後計數器局部使用完後仍是會被減量的。
可是在局部變量的做用域中,若是對象的計數器爲0那就必需要進行增量操做對變量進行保護了。
像這樣的狀況,什麼時候對對象的計數器增量,什麼時候減量,徹底能夠有編程人員本身判斷,若是不能判斷則就按照規則來。