說明
- python源碼版本:3.8.3
參考:《python源碼剖析》
python運(yùn)行機(jī)制
當(dāng)python代碼運(yùn)行時(shí),會(huì)將代碼轉(zhuǎn)成一堆的字節(jié)指令走贪,然后通過PyEval_EvalFrame
函數(shù)執(zhí)行里面的內(nèi)容佑刷,源碼如下:
// ~/Python/ceval.c
// python代碼執(zhí)行的入口函數(shù)
PyObject *
PyEval_EvalFrame(PyFrameObject *f) {
// 傳入一個(gè)棧幀對(duì)象隙袁,并傳入PyEval_EvalFrameEx函數(shù)執(zhí)行
return PyEval_EvalFrameEx(f, 0);
}
PyObject *
PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
{
// 獲取解釋器對(duì)象臊诊,可以理解為當(dāng)前執(zhí)行的進(jìn)程
PyInterpreterState *interp = _PyInterpreterState_GET_UNSAFE();
// 執(zhí)行執(zhí)行棧幀芽世,eval_frame實(shí)際上就是_PyEval_EvalFrameDefault,定義如下:
// interp->eval_frame = _PyEval_EvalFrameDefault;
return interp->eval_frame(f, throwflag);
}
而_PyEval_EvalFrameDefault
函數(shù)主要是由for
循環(huán)和一個(gè)巨大的switch
語句組成的藻三,其中switch
語句里定義了對(duì)各種opcode
執(zhí)行的操作洪橘,因此通過for
循環(huán)不斷地從switch
中尋找對(duì)應(yīng)的指令操作執(zhí)行跪者,就組成了python程序(實(shí)際上是模擬了一個(gè)執(zhí)行的函數(shù)棧對(duì)象),部分源碼如下:
// ~/Python/ceval.c
PyObject* _Py_HOT_FUNCTION
_PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
{
...
// 主循環(huán)
main_loop:
for (;;) {
...
// 根據(jù)opcode執(zhí)行對(duì)應(yīng)的操作
dispatch_opcode:
...
switch (opcode) {
// NOP指令行為
case TARGET(NOP): {
FAST_DISPATCH();
}
// LOAD_FAST指令行為
case TARGET(LOAD_FAST): {
...
}
// LOAD_CONST指令行為
case TARGET(LOAD_CONST): {
...
}
...
}
}
code對(duì)象
python中code對(duì)象記錄著代碼的運(yùn)行環(huán)境熄求、運(yùn)行指令等渣玲,是python程序運(yùn)行的基石,其主要屬性如下:
co_consts 常量表
co_varnames 變量名表
co_names 命名空間表
co_nlocals 局部變量的數(shù)量
co_filename code所在文件
co_name code對(duì)應(yīng)名稱(函數(shù)名弟晚、模塊名忘衍、類名)
co_argcount 位置參數(shù)數(shù)量(不包括擴(kuò)展位置參數(shù)和擴(kuò)展鍵參數(shù),這兩個(gè)參數(shù)實(shí)際上是作為局部變量存在local區(qū)里)
co_kwonlyargcount 鍵參數(shù)數(shù)量
co_code 代碼的字節(jié)碼指令(包括opcode和oparg)
co_firstlineno code代碼第一行在文件中的行數(shù)
co_cellvars 在外層函數(shù)里記錄的嵌套作用域會(huì)使用到的變量名集合
co_freevars 在嵌套函數(shù)里記錄使用到的外層作用域的變量名集合
co_stacksize 棧的大小
co_lnotab 字節(jié)碼指令和行號(hào)的對(duì)應(yīng)關(guān)系
co_flags 對(duì)象的信息(例如記錄了是函數(shù)還是生成器卿城?是否有擴(kuò)展參數(shù)等)
co_flags
每一位標(biāo)志定義如下:
# 可以在inspect模塊下查看
1=optimized
2=newlocals
4=*arg
8=**arg
16=nested
32=generator
64=nofree
128=coroutine
256=iterable_coroutine
512=async_generator
co_cellvars/co_freevars
co_freevars
是每個(gè)閉包函數(shù)中記錄當(dāng)前閉包函數(shù)里會(huì)使用到的外部變量枚钓,而co_cellvars
則是當(dāng)前函數(shù)中記錄所有嵌套函數(shù)內(nèi)部會(huì)使用到當(dāng)前函數(shù)的變量,如果只有一個(gè)閉包函數(shù)使用到了外部函數(shù)的變量時(shí)藻雪,co_cellvars
和co_freevars
的內(nèi)容可能一樣秘噪,舉例:
def test(a, b=2, c=3):
d = [1,2,3,4,5]
e = {}
def cell_test1():
d = (a, c)
def cell_test2():
f = e
print("test:", test.__code__.co_cellvars)
print("cell_test1:", cell_test1.__code__.co_freevars)
print("cell_test2:", cell_test2.__code__.co_freevars)
test(1)
# test: ('a', 'c', 'e')
# cell_test1: ('a', 'c')
# cell_test2: ('e',)
可以看出cell_test1
里使用了a
和c
,cell_test2
里使用了e
勉耀,而test
里則記錄了所有被使用到的變量
opcode
根據(jù)前面的介紹可以知道python程序的運(yùn)行實(shí)際上就是不斷地讀取code
對(duì)象中記錄的opcode
以及對(duì)應(yīng)的oparg
來執(zhí)行相應(yīng)的動(dòng)作指煎,例如下面一段代碼:
def test(a, b, c):
pass
def run():
# a并沒有定義,但只要沒調(diào)用便斥,也就不會(huì)檢查a是否存在
a()
test(1, 2, 3)
from dis import dis
dis(run)
可以看出run
函數(shù)生成對(duì)應(yīng)的opcode如下:
# 四列依次為:指令偏移至壤;opcode對(duì)應(yīng)指令;oparg枢纠;oparg對(duì)應(yīng)的值
0 LOAD_GLOBAL 0 (a) # 載入全局區(qū)的a
2 CALL_FUNCTION 0 # 對(duì)a進(jìn)行函數(shù)調(diào)用像街,并且由于沒有參數(shù),所以傳入?yún)?shù)0
4 POP_TOP # 彈出棧頂元素(CALL_FUNCTION執(zhí)行完會(huì)將返回值壓入棧頂)
6 LOAD_GLOBAL 1 (test) # 載入test
8 LOAD_CONST 1 (1) # 依次將3個(gè)參數(shù)壓入棧
10 LOAD_CONST 2 (2)
12 LOAD_CONST 3 (3)
14 CALL_FUNCTION 3 # 函數(shù)調(diào)用晋渺,并從棧中依次取出3個(gè)元素
16 POP_TOP
18 LOAD_CONST 0 (None)
20 RETURN_VALUE
并且可以看出run
函數(shù)中的a
函數(shù)并沒有定義镰绎,但是并不會(huì)報(bào)錯(cuò),這是因?yàn)閜ython在對(duì)run
函數(shù)解析對(duì)應(yīng)的code
對(duì)象時(shí)木西,只會(huì)對(duì)語法進(jìn)行解析畴栖,并生成對(duì)應(yīng)的opcode
和oparg
組成的字節(jié)流,而不會(huì)去管解析的語句是否能夠成功執(zhí)行八千,所以只有在真正執(zhí)行的時(shí)候吗讶,才會(huì)知道是否存在問題
注:
為了便于解析,python3.6開始規(guī)定每一組指令單位都是2個(gè)字節(jié)(python3.6之前是不固定的)恋捆,通過前面示例解析的opcode
(第一列)也可以看出每條指令偏移的結(jié)果是公差為2的等差數(shù)列照皆,其中opcode
和oparg
分別占一個(gè)字節(jié),源碼如下:
// 獲取當(dāng)前指令的opcode和oparg沸停,并指向下一條指令位置
#define NEXTOPARG() do { \
// 獲取opcode+oparg組成的指令(uint_16_t類型膜毁,總共2個(gè)字節(jié))
_Py_CODEUNIT word = *next_instr; \
// 獲取opcode
opcode = _Py_OPCODE(word); \
// 獲取oparg
oparg = _Py_OPARG(word); \
// 指向下一條指令
next_instr++; \
} while (0)
// 獲取opcode、oparg邏輯,根據(jù)大/小端模式爽茴,取值方式有所不同
#ifdef WORDS_BIGENDIAN
# define _Py_OPCODE(word) ((word) >> 8)
# define _Py_OPARG(word) ((word) & 255)
#else
# define _Py_OPCODE(word) ((word) & 255)
# define _Py_OPARG(word) ((word) >> 8)
#endif
每個(gè)opcode
都有對(duì)應(yīng)的編號(hào)葬凳,其中編號(hào)大于90
的屬于有參數(shù)指令,對(duì)于無參數(shù)的指令室奏,默認(rèn)會(huì)以0
作為參數(shù),從而保證oparg
部分占有1個(gè)字節(jié)劲装,源碼如下:
#define HAVE_ARGUMENT 90
// 判斷指令是否存在參數(shù)
#define HAS_ARG(op) ((op) >= HAVE_ARGUMENT)
常見opcode介紹
LOAD_FAST 載入局部變量胧沫,從棧幀對(duì)象的棧空間(f_localsplus)中取出數(shù)據(jù)
LOAD_CONST 載入常量表內(nèi)容(code對(duì)象的co_consts當(dāng)中)
LOAD_NAME 載入命名空間變量(code對(duì)象的co_names當(dāng)中)
STORE_NAME 定義命名空間變量
STORE_FAST 定義局部變量
POP_TOP 彈出棧頂元素
RETURN_VALUE 返回棧頂元素
BUILD_LIST 創(chuàng)建一個(gè)列表占业,傳入一個(gè)參數(shù)代表列表內(nèi)容長度
BUILD_MAP 創(chuàng)建一個(gè)字典
LOAD_ATTR 載入屬性
MAKE_FUNCTION 創(chuàng)建函數(shù)
CALL_FUNCTION 調(diào)用函數(shù)
...
這些opcode
都會(huì)在python那個(gè)巨大的switch
當(dāng)中有對(duì)應(yīng)的操作绒怨,例如CALL_FUNCTION
的源碼操作如下:
// 調(diào)用函數(shù)
case TARGET(CALL_FUNCTION): {
PREDICTED(CALL_FUNCTION);
PyObject **sp, *res;
// 記錄當(dāng)前函數(shù)指針的位置
sp = stack_pointer;
// 調(diào)用函數(shù),并將當(dāng)前棧頂指針位置傳入
res = call_function(tstate, &sp, oparg, NULL);
// 函數(shù)執(zhí)行完畢谦疾,將棧頂指針恢復(fù)到上一層函數(shù)的位置
stack_pointer = sp;
// 將結(jié)果返回值壓入棧頂
PUSH(res);
if (res == NULL) {
goto error;
}
// 繼續(xù)下一條指令
DISPATCH();
}
可以看出函數(shù)在執(zhí)行完畢以后南蹂,會(huì)執(zhí)行一次PUSH(res)
操作,即把函數(shù)的返回值壓入棧頂念恍,這也是Python的函數(shù)為什么必然有返回值的原因六剥,如果不寫return
語句,編譯器也會(huì)自動(dòng)在函數(shù)的最后加上將None
壓入棧的指令峰伙。而函數(shù)執(zhí)行結(jié)束以后疗疟,因?yàn)橹粫?huì)進(jìn)行一次壓棧操作,所以不支持多個(gè)返回值瞳氓,在這種情況下策彤,我們一般通過返回一個(gè)序列對(duì)象來實(shí)現(xiàn)返回多個(gè)值的目的。
opcode作用
opcode
是我們參考程序執(zhí)行效率的重要指標(biāo)之一匣摘,例如我們通過opcode
對(duì)創(chuàng)建一定長度列表的代碼進(jìn)行優(yōu)化:
import time
def test():
l = 10000000
start = time.time()
li = []
for i in range(l):
li.append(i)
print(time.time() - start)
test()
# 1.726109504699707
可以發(fā)現(xiàn)程序的平均執(zhí)行時(shí)間為1.7s
左右(不同電腦性能會(huì)有差異)店诗,分析opcode
如下(截取主要部分):
31 0 LOAD_CONST 1 (10000000)
2 STORE_FAST 0 (l)
33 4 BUILD_LIST 0
6 STORE_FAST 1 (li)
34 8 SETUP_LOOP 26 (to 36)
10 LOAD_GLOBAL 0 (range)
12 LOAD_FAST 0 (l)
14 CALL_FUNCTION 1
16 GET_ITER
>> 18 FOR_ITER 14 (to 34)
20 STORE_FAST 2 (i)
35 22 LOAD_FAST 1 (li)
24 LOAD_ATTR 1 (append)
26 LOAD_FAST 2 (i)
28 CALL_FUNCTION 1
30 POP_TOP
32 JUMP_ABSOLUTE 18
>> 34 POP_BLOCK
由于循環(huán)量特別大,所以主要就是減少單次循環(huán)內(nèi)的操作音榜,這里我們看到每次循環(huán)時(shí)都會(huì)進(jìn)行一次LOAD_ATTR
操作庞瘸,即每次for
循環(huán)都要查找一遍append
屬性,于是我們可以先將for
循環(huán)內(nèi)的屬性查找提取出來囊咏,代碼如下:
import time
def test():
l = 10000000
start = time.time()
li = []
# 將load_attr操作提取出來
app = li.append
for i in range(l):
# 這里就不用再每次查找append屬性了
app(i)
print(time.time() - start)
test()
# 1.4092960357666016
可以看到效率有小幅度的增長恕洲,然后會(huì)發(fā)現(xiàn)好像沒有什么可以優(yōu)化的了...于是我們可以嘗試使用列表表達(dá)式來看看效果:
import time
def test():
l = 10000000
start = time.time()
[i for i in range(l)]
print(time.time() - start)
test()
# 1.1854212284088135
可以看到效率又有了一定的提升,這里我們可以剖析一下為什么列表表達(dá)式效率比for
循環(huán)的要快梅割,首先我們看一下使用列表表達(dá)式對(duì)應(yīng)的opcode
:
4 0 LOAD_CONST 1 (10000000)
2 STORE_FAST 0 (l)
6 4 LOAD_CONST 2 (<code object <listcomp> at 0x000001CD2FEA4930, file "xxx.py", line 6>)
6 LOAD_CONST 3 ('test.<locals>.<listcomp>')
8 MAKE_FUNCTION 0
10 LOAD_GLOBAL 0 (range)
12 LOAD_FAST 0 (l)
14 CALL_FUNCTION 1
16 GET_ITER
18 CALL_FUNCTION 1
20 POP_TOP
可以看到列表表達(dá)式的本質(zhì)是使用了一個(gè)listcomp
函數(shù)來進(jìn)行創(chuàng)建列表的操作霜第,而listcomp
對(duì)應(yīng)的指令操作如下:
1 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 8 (to 14)
6 STORE_FAST 1 (i)
8 LOAD_FAST 1 (i)
10 LIST_APPEND 2
12 JUMP_ABSOLUTE 4
>> 14 RETURN_VALUE
可以看到listcomp
當(dāng)中也是通過for
循環(huán)迭代調(diào)用append
函數(shù)將內(nèi)容添加到一個(gè)列表當(dāng)中,和我們使用for
循環(huán)創(chuàng)建列表的操作看起來幾乎一樣户辞,而差距的關(guān)鍵就在于列表表達(dá)式添加元素時(shí)使用的是LIST_APPEND
指令泌类,而我們調(diào)用append
方法時(shí)使用的是CALL_FUNCTION
指令調(diào)用append
函數(shù),LIST_APPEND
指令操作源碼如下:
// 列表的append操作
case TARGET(LIST_APPEND): {
PyObject *v = POP();
PyObject *list = PEEK(oparg);
int err;
// 調(diào)用list提供的C接口PyList_Append來添加元素
err = PyList_Append(list, v);
Py_DECREF(v);
if (err != 0)
goto error;
PREDICT(JUMP_ABSOLUTE);
DISPATCH();
}
可以看到該指令直接調(diào)用了底層的PyList_Append
函數(shù)接口對(duì)列表進(jìn)行元素的添加,而我們使用的CALL_FUNCTION
指令相對(duì)來說就十分繁瑣:雖然最終也是調(diào)用list
提供的list_append
接口(PyList_Append
和list_append
的邏輯差不多)刃榨,但在執(zhí)行list_append
之前卻要進(jìn)行一系列的預(yù)操作弹砚,如:開辟、回收棧幀對(duì)象空間(PyList_Append
是在C語言級(jí)別的検嘞#空間開辟,而CALL_FUNCTION
則是在Python棧幀對(duì)象級(jí)別上開辟)苞轿、參數(shù)接收茅诱、函數(shù)處理等一系列操作,無形中就降低了執(zhí)行的效率搬卒,所以這就是為什么列表表達(dá)式比for
循環(huán)更加快的原因
opcode優(yōu)化參考
函數(shù)
棧幀對(duì)象
在前面可以知道python程序會(huì)在模擬的函數(shù)棧對(duì)象當(dāng)中不斷地執(zhí)行字節(jié)碼瑟俭,而這個(gè)棧對(duì)象的實(shí)現(xiàn)就是一個(gè)PyFrameObject
,定義如下:
typedef struct _frame {
PyObject_VAR_HEAD
// 指向上一層棧幀
struct _frame *f_back; /* previous frame, or NULL */
// 當(dāng)前棧幀的code對(duì)象
PyCodeObject *f_code; /* code segment */
// 當(dāng)前棧幀的內(nèi)置函數(shù)區(qū)
PyObject *f_builtins; /* builtin symbol table (PyDictObject) */
// 當(dāng)前棧幀的全局區(qū)(全局空間契邀,xxx_global操作的空間千埃,如load_global)
PyObject *f_globals; /* global symbol table (PyDictObject) */
// 當(dāng)前棧幀的局部區(qū)(命名空間笋籽,xxx_name操作的空間,如load_name)
PyObject *f_locals; /* local symbol table (any mapping) */
// 指向棧底
PyObject **f_valuestack; /* points after the last local */
/* Next free slot in f_valuestack. Frame creation sets to f_valuestack.
Frame evaluation usually NULLs it, but a frame that yields sets it
to the current stack top. */
// 指向棧頂
PyObject **f_stacktop;
PyObject *f_trace; /* Trace function */
char f_trace_lines; /* Emit per-line trace events? */
char f_trace_opcodes; /* Emit per-opcode trace events? */
/* Borrowed reference to a generator, or NULL */
PyObject *f_gen;
// 棧幀中上一條執(zhí)行完指令的偏移
int f_lasti; /* Last instruction if called */
/* Call PyFrame_GetLineNumber() instead of reading this field
directly. As of 2.3 f_lineno is only valid when tracing is
active (i.e. when f_trace is set). At other times we use
PyCode_Addr2Line to calculate the line from the current
bytecode index. */
// 當(dāng)前行
int f_lineno; /* Current line number */
// 棧的索引
int f_iblock; /* index in f_blockstack */
// 是否正在執(zhí)行
char f_executing; /* whether the frame is still executing */
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
// 動(dòng)態(tài)內(nèi)存,維護(hù)需要的空間(當(dāng)前棧幀的椀赘纾空間咆贬,xxx_fast操作的空間救赐,如load_fast)
PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */
} PyFrameObject;
我們可以通過sys
模塊查看當(dāng)前執(zhí)行的棧幀尿这,舉例:
import sys
import inspect
def test():
# 獲取test函數(shù)執(zhí)行棧幀
a = sys._getframe()
def aaa():
b = sys._getframe()
# 可以看到aaa函數(shù)的上一層棧幀就是test函數(shù)的棧幀
print(b.f_back is a)
# 當(dāng)前調(diào)用堆棧的頂部棧幀就是aaa函數(shù)的棧幀
print(inspect.stack()[0][0] is b)
# 打印調(diào)用堆棧所對(duì)應(yīng)的執(zhí)行空間
for stack in inspect.stack():
print(stack.function)
aaa()
test()
# True
# True
# aaa
# test
# <module>
函數(shù)本質(zhì)
當(dāng)創(chuàng)建函數(shù)時(shí),函數(shù)對(duì)象就會(huì)綁定相關(guān)的執(zhí)行接口_PyFunction_Vectorcall
允瞧,當(dāng)函數(shù)被調(diào)用時(shí)简软,就是調(diào)用_PyFunction_Vectorcall
函數(shù),函數(shù)中將會(huì)創(chuàng)建對(duì)應(yīng)的棧幀對(duì)象述暂,然后在該棧幀下依次執(zhí)行函數(shù)指向的code
對(duì)象里的字節(jié)指令(即調(diào)用PyEval_EvalFrameEx
函數(shù)痹升,在巨大的switch
里執(zhí)行指令),源碼如下:
// 函數(shù)調(diào)用接口
PyObject *
_PyFunction_Vectorcall(PyObject *func, PyObject* const* stack,
size_t nargsf, PyObject *kwnames)
{
// 獲取code對(duì)象
PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func);
PyObject *globals = PyFunction_GET_GLOBALS(func);
// 獲取參數(shù)默認(rèn)值
PyObject *argdefs = PyFunction_GET_DEFAULTS(func);
PyObject *kwdefs, *closure, *name, *qualname;
PyObject **d;
Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
Py_ssize_t nd;
assert(PyFunction_Check(func));
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
assert(nargs >= 0);
assert(kwnames == NULL || PyTuple_CheckExact(kwnames));
assert((nargs == 0 && nkwargs == 0) || stack != NULL);
/* kwnames must only contains str strings, no subclass, and all keys must
be unique */
if (co->co_kwonlyargcount == 0 && nkwargs == 0 &&
(co->co_flags & ~PyCF_MASK) == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE))
{
if (argdefs == NULL && co->co_argcount == nargs) {
// 函數(shù)調(diào)用的快速通道畦韭,內(nèi)部將會(huì)調(diào)用PyEval_EvalFrameEx
return function_code_fastcall(co, stack, nargs, globals);
}
else if (nargs == 0 && argdefs != NULL
&& co->co_argcount == PyTuple_GET_SIZE(argdefs)) {
/* function called with no arguments, but all parameters have
a default value: use default values as arguments .*/
stack = _PyTuple_ITEMS(argdefs);
return function_code_fastcall(co, stack, PyTuple_GET_SIZE(argdefs),
globals);
}
}
// 有鍵參數(shù)的情況
kwdefs = PyFunction_GET_KW_DEFAULTS(func);
closure = PyFunction_GET_CLOSURE(func);
name = ((PyFunctionObject *)func) -> func_name;
qualname = ((PyFunctionObject *)func) -> func_qualname;
if (argdefs != NULL) {
d = _PyTuple_ITEMS(argdefs);
nd = PyTuple_GET_SIZE(argdefs);
}
else {
d = NULL;
nd = 0;
}
// 執(zhí)行函數(shù)中的code內(nèi)容疼蛾,內(nèi)部將會(huì)調(diào)用PyEval_EvalFrameEx
return _PyEval_EvalCodeWithName((PyObject*)co, globals, (PyObject *)NULL,
stack, nargs,
nkwargs ? _PyTuple_ITEMS(kwnames) : NULL,
stack + nargs,
nkwargs, 1,
d, (int)nd, kwdefs,
closure, name, qualname);
}
// 函數(shù)調(diào)用快速通道主邏輯
static PyObject* _Py_HOT_FUNCTION
function_code_fastcall(PyCodeObject *co, PyObject *const *args, Py_ssize_t nargs,
PyObject *globals)
{
PyFrameObject *f;
// 獲取當(dāng)前線程
PyThreadState *tstate = _PyThreadState_GET();
PyObject **fastlocals;
Py_ssize_t i;
PyObject *result;
assert(globals != NULL);
/* XXX Perhaps we should create a specialized
_PyFrame_New_NoTrack() that doesn't take locals, but does
take builtins without sanity checking them.
*/
assert(tstate != NULL);
// 創(chuàng)建一個(gè)棧幀對(duì)象
f = _PyFrame_New_NoTrack(tstate, co, globals, NULL);
if (f == NULL) {
return NULL;
}
fastlocals = f->f_localsplus;
for (i = 0; i < nargs; i++) {
Py_INCREF(*args);
fastlocals[i] = *args++;
}
// 獲取執(zhí)行結(jié)果
result = PyEval_EvalFrameEx(f,0);
if (Py_REFCNT(f) > 1) {
Py_DECREF(f);
_PyObject_GC_TRACK(f);
}
else {
// 遞歸深度控制
++tstate->recursion_depth;
Py_DECREF(f);
--tstate->recursion_depth;
}
return result;
}
所以執(zhí)行函數(shù)的本質(zhì)就是執(zhí)行code
對(duì)象里的字節(jié)指令,例如我們可以通過修改函數(shù)的code
對(duì)象來修改函數(shù)的功能艺配,舉例:
def test1(a=1):
print("test1", a)
def test2(a=100):
print("test2", a)
# 修改test1的code對(duì)象
test1.__code__ = test2.__code__
test1()
# test2 1
可以看到test1
最終執(zhí)行的是test2
的指令察郁,但因?yàn)檫@里我們只是進(jìn)行了code
對(duì)象的修改(即只是修改了執(zhí)行的指令),而默認(rèn)參數(shù)空間還是用函數(shù)test1
對(duì)象的转唉,所以輸出的a
結(jié)果是1
而不是100
皮钠,這樣我們就悄悄地實(shí)現(xiàn)了對(duì)函數(shù)的“移花接木”
閉包
例如下面的函數(shù),test
函數(shù)嵌套了func
函數(shù)赠法,func
是閉包函數(shù):
def test(a = 2, b = 3):
c = "aaa"
d = 1
e = 5
def func():
nonlocal a
print(locals())
b
c
d = 4
return func
clo = test()
clo()
# {'c': 'aaa', 'b': 3, 'a': 2}
其中函數(shù)test
的字節(jié)碼:
2 0 LOAD_CONST 1 ('aaa')
2 STORE_DEREF 2 (c)
3 4 LOAD_CONST 2 (1)
6 STORE_FAST 2 (d)
4 8 LOAD_CONST 3 (5)
10 STORE_FAST 3 (e)
5 12 LOAD_CLOSURE 0 (a)
14 LOAD_CLOSURE 1 (b)
16 LOAD_CLOSURE 2 (c)
18 BUILD_TUPLE 3
20 LOAD_CONST 4 (<code object func at 0x000001D669AB49C0, file "xxx.py", line 5>)
22 LOAD_CONST 5 ('test.<locals>.func')
24 MAKE_FUNCTION 8
26 STORE_FAST 4 (func)
11 28 LOAD_FAST 4 (func)
30 RETURN_VALUE
通過5
的前幾行可以看出麦轰,閉包里載入了變量a
/b
/c
,而d
因?yàn)樵陂]包函數(shù)里將會(huì)進(jìn)行賦值操作,所以閉包函數(shù)會(huì)默認(rèn)認(rèn)為d
是在閉包函數(shù)內(nèi)部創(chuàng)建的款侵,因此不需要從外層函數(shù)載入末荐,變量e
則是因?yàn)闆]有用到,因此也就沒必要進(jìn)行載入新锈,閉包存儲(chǔ)的數(shù)據(jù)可以在閉包函數(shù)里通過locals()
獲取甲脏,但里面會(huì)包括函數(shù)里的其他局部變量,如果希望只獲取外部載入閉包的相關(guān)內(nèi)容壕鹉,可以通過__closure__
屬性獲取剃幌,舉例:
def test(a = 2, b = 3):
c = 5
d = 1
e = 5
def func():
nonlocal a
print(locals())
b
c
d = 4
print(locals())
return func
clo = test()
print([each.cell_contents for each in clo.__closure__])
# 輸出所有閉包包含的內(nèi)容
clo()
# [2, 3, 5]
# {'c': 5, 'b': 3, 'a': 2}
# {'c': 5, 'b': 3, 'a': 2, 'd': 4}
函數(shù)參數(shù)限制
在python3.6之前,由于函數(shù)的參數(shù)數(shù)量是通過2字節(jié)的oparg
來進(jìn)行記錄的晾浴,其中低8位用于記錄位置參數(shù)的數(shù)量,高8位用于記錄鍵參數(shù)的數(shù)量牍白,因此參數(shù)數(shù)量不允許超過255脊凰,獲取參數(shù)邏輯如下:
// ~/python/ceval.c
// 3.6之前的獲取參數(shù)方式
static PyObject *
call_function(PyObject ***pp_stack, int oparg
#ifdef WITH_TSC
, uint64* pintr0, uint64* pintr1
#endif
)
{
// 低8位是位置參數(shù)數(shù)量
int na = oparg & 0xff;
// 高8位是鍵參數(shù)數(shù)量
int nk = (oparg>>8) & 0xff;
int n = na + 2 * nk;
...
}
因此如果參數(shù)超過255,那么獲取參數(shù)數(shù)量時(shí)就會(huì)出現(xiàn)問題茂腥,所以python在解析代碼時(shí)狸涌,如果超過255個(gè)參數(shù)就會(huì)拋出語法異常,舉例:
def aaa(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a50, a51, a52, a53, a54, a55, a56, a57, a58, a59, a60, a61, a62, a63, a64, a65, a66, a67, a68, a69, a70, a71, a72, a73, a74, a75, a76, a77, a78, a79, a80, a81, a82, a83, a84, a85, a86, a87, a88, a89, a90, a91, a92, a93, a94, a95, a96, a97, a98, a99, a100, a101, a102, a103, a104, a105, a106, a107, a108, a109, a110, a111, a112, a113, a114, a115, a116, a117, a118, a119, a120, a121, a122, a123, a124, a125, a126, a127, a128, a129, a130, a131, a132, a133, a134, a135, a136, a137, a138, a139, a140, a141, a142, a143, a144, a145, a146, a147, a148, a149, a150, a151, a152, a153, a154, a155, a156, a157, a158, a159, a160, a161, a162, a163, a164, a165, a166, a167, a168, a169, a170, a171, a172, a173, a174, a175, a176, a177, a178, a179, a180, a181, a182, a183, a184, a185, a186, a187, a188, a189, a190, a191, a192, a193, a194, a195, a196, a197, a198, a199, a200, a201, a202, a203, a204, a205, a206, a207, a208, a209, a210, a211, a212, a213, a214, a215, a216, a217, a218, a219, a220, a221, a222, a223, a224, a225, a226, a227, a228, a229, a230, a231, a232, a233, a234, a235, a236, a237, a238, a239, a240, a241, a242, a243, a244, a245, a246, a247, a248, a249, a250, a251, a252, a253, a254, a255):
pass
# SyntaxError: more than 255 arguments
注意這里是在語法解析層面進(jìn)行的檢查最岗,而不是在函數(shù)執(zhí)行的時(shí)候進(jìn)行檢查帕胆,解析源碼如下:
// ~/python/ast.c
static expr_ty
ast_for_call(struct compiling *c, const node *n, expr_ty func)
{
...
nargs = 0;
nkeywords = 0;
ngens = 0;
...
// 參數(shù)總數(shù)不允許超過255個(gè)
if (nargs + nkeywords + ngens > 255) {
ast_error(n, "more than 255 arguments");
return NULL;
}
...
python3.7中為了解決oparg
的限制問題,當(dāng)oparg
超過255時(shí)般渡,會(huì)通過EXTENDED_ARG
指令來進(jìn)行擴(kuò)充懒豹,指令源碼如下:
// 當(dāng)oparg不夠裝的時(shí)候(例如oparg>=255),對(duì)oparg加8個(gè)字節(jié)進(jìn)行擴(kuò)充
// 例如:LOAD_NAME 257是不能直接執(zhí)行的驯用,因?yàn)閛parg上限是255脸秽,因此就會(huì)轉(zhuǎn)換成執(zhí)行下面指令:
// EXTENDED_ARG 1
// LOAD_NAME 1
// 解釋:EXTENDED_ARG會(huì)將oparg先變成256(00000001 00000000),并取下一個(gè)指令的opcode和oparg蝴乔,
// 然后256與下一個(gè)的oparg進(jìn)行或運(yùn)算记餐,例如這里就變成256 | 1,結(jié)果就是257薇正,所以最終就執(zhí)行了:LOAD_NAME 257
case TARGET(EXTENDED_ARG): {
int oldoparg = oparg;
NEXTOPARG();
oparg |= oldoparg << 8;
goto dispatch_opcode;
}
還是對(duì)前面那個(gè)函數(shù)參數(shù)超過255的示例片酝,我們可以查看opcode
的操作:
...
510 LOAD_NAME 255 (a254)
512 EXTENDED_ARG 1
514 LOAD_NAME 256 (a255)
516 EXTENDED_ARG 1
518 CALL_FUNCTION 256
...
這里可以看出oparg已經(jīng)可以超過255了,而對(duì)應(yīng)的字節(jié)碼如下:
[..., 144, 1, 101, 0, 144, 1, 131, 0, ...]
# 144代表EXTENDED_ARG挖腰,101代表LOAD_NAME雕沿,131代表CALL_FUNCTION
而[144, 1, 131, 0]
就可以解析為:
EXTENDED_ARG 1
CALL_FUNCTION 0
即:
CALL_FUNCTION 256
并且在python3.7以后也刪除了對(duì)應(yīng)的語法解析,所以之后對(duì)于參數(shù)數(shù)量就不再有255長度的限制了
類機(jī)制
mro機(jī)制
python中的類對(duì)象支持多繼承曙聂,而在多繼承當(dāng)中晦炊,確認(rèn)類的繼承順序是件十分復(fù)雜的問題,在python中就采用了C3算法來確認(rèn)繼承順序,源碼如下:
// 確認(rèn)mro順序的邏輯
static PyObject *
mro_implementation(PyTypeObject *type)
{
PyObject *result;
PyObject *bases;
PyObject **to_merge;
Py_ssize_t i, n;
if (type->tp_dict == NULL) {
if (PyType_Ready(type) < 0)
return NULL;
}
bases = type->tp_bases;
assert(PyTuple_Check(bases));
n = PyTuple_GET_SIZE(bases);
// 直接父類的mro存在確認(rèn)
for (i = 0; i < n; i++) {
PyTypeObject *base = (PyTypeObject *)PyTuple_GET_ITEM(bases, i);
if (base->tp_mro == NULL) {
PyErr_Format(PyExc_TypeError,
"Cannot extend an incomplete type '%.100s'",
base->tp_name);
return NULL;
}
assert(PyTuple_Check(base->tp_mro));
}
// 單繼承的情況断国,直接將當(dāng)前類+父類的mro組成元組返回
if (n == 1) {
/* Fast path: if there is a single base, constructing the MRO
* is trivial.
*/
PyTypeObject *base = (PyTypeObject *)PyTuple_GET_ITEM(bases, 0);
Py_ssize_t k = PyTuple_GET_SIZE(base->tp_mro);
result = PyTuple_New(k + 1);
if (result == NULL) {
return NULL;
}
Py_INCREF(type);
PyTuple_SET_ITEM(result, 0, (PyObject *) type);
for (i = 0; i < k; i++) {
PyObject *cls = PyTuple_GET_ITEM(base->tp_mro, i);
Py_INCREF(cls);
PyTuple_SET_ITEM(result, i + 1, cls);
}
return result;
}
/* This is just a basic sanity check. */
if (check_duplicates(bases) < 0) {
return NULL;
}
/* Find a superclass linearization that honors the constraints
of the explicit tuples of bases and the constraints implied by
each base class.
to_merge is an array of tuples, where each tuple is a superclass
linearization implied by a base class. The last element of
to_merge is the declared tuple of bases.
*/
// 多繼承
to_merge = PyMem_New(PyObject *, n + 1);
if (to_merge == NULL) {
PyErr_NoMemory();
return NULL;
}
// 將所有直接父類的mro列表存到merge列表當(dāng)中
for (i = 0; i < n; i++) {
PyTypeObject *base = (PyTypeObject *)PyTuple_GET_ITEM(bases, i);
to_merge[i] = base->tp_mro;
}
// 將直接父類列表頁存到merge列表當(dāng)中
// 例如A(B, C)就變成:[orm(B), orm(C), [B, C]]
to_merge[n] = bases;
result = PyList_New(1);
if (result == NULL) {
PyMem_Del(to_merge);
return NULL;
}
Py_INCREF(type);
PyList_SET_ITEM(result, 0, (PyObject *)type);
// 對(duì)merge列表進(jìn)行merge操作
if (pmerge(result, to_merge, n + 1) < 0) {
Py_CLEAR(result);
}
PyMem_Del(to_merge);
return result;
}
// C3算法的merge邏輯
static int
pmerge(PyObject *acc, PyObject **to_merge, Py_ssize_t to_merge_size)
{
int res = 0;
Py_ssize_t i, j, empty_cnt;
int *remain;
/* remain stores an index into each sublist of to_merge.
remain[i] is the index of the next base in to_merge[i]
that is not included in acc.
*/
remain = PyMem_New(int, to_merge_size);
if (remain == NULL) {
PyErr_NoMemory();
return -1;
}
// 設(shè)置每個(gè)merge組中取出了n個(gè)以后的起始位置
// (例如[[A,B], [A,C]]贤姆,起始是remain為:[0, 0],如果A被取出并加入到了mro列表稳衬,那么remain就變成了[1,1],那么下一次這兩組都是從第二個(gè)開始找候選)
for (i = 0; i < to_merge_size; i++)
remain[i] = 0;
again:
empty_cnt = 0;
for (i = 0; i < to_merge_size; i++) {
PyObject *candidate;
PyObject *cur_tuple = to_merge[i];
// 如果當(dāng)前組的類都取出了霞捡,則從下一個(gè)組中尋找候選
if (remain[i] >= PyTuple_GET_SIZE(cur_tuple)) {
empty_cnt++;
continue;
}
/* Choose next candidate for MRO.
The input sequences alone can determine the choice.
If not, choose the class which appears in the MRO
of the earliest direct superclass of the new class.
*/
// 選出當(dāng)前遍歷到的那一組的未被取出的第一個(gè)作為候選人
candidate = PyTuple_GET_ITEM(cur_tuple, remain[i]);
// 如果該候選人在別的組里的尾部(非第一個(gè)未被取出的)存在,則尋找下一個(gè)候選人
for (j = 0; j < to_merge_size; j++) {
PyObject *j_lst = to_merge[j];
if (tail_contains(j_lst, remain[j], candidate))
goto skip; /* continue outer loop */
}
// 如果是符合條件的候選薄疚,則添加進(jìn)mro列表
res = PyList_Append(acc, candidate);
if (res < 0)
goto out;
// 將所有組中的該候選取出(候選的索引起始位置變成該候選的位置+1)
for (j = 0; j < to_merge_size; j++) {
PyObject *j_lst = to_merge[j];
if (remain[j] < PyTuple_GET_SIZE(j_lst) &&
PyTuple_GET_ITEM(j_lst, remain[j]) == candidate) {
remain[j]++;
}
}
goto again;
skip: ;
}
// 能到這則merge中所有的組里的類都必須全部取出
if (empty_cnt != to_merge_size) {
set_mro_error(to_merge, to_merge_size, remain);
res = -1;
}
out:
PyMem_Del(remain);
return res;
}
查找屬性
類對(duì)象的屬性查找的默認(rèn)邏輯源碼如下:
// 類對(duì)象getattr主邏輯
// 尋找順序:元類數(shù)據(jù)描述符->基類mro上屬性->元類屬性
static PyObject *
type_getattro(PyTypeObject *type, PyObject *name)
{
// 獲取對(duì)象的元類
PyTypeObject *metatype = Py_TYPE(type);
PyObject *meta_attribute, *attribute;
descrgetfunc meta_get;
PyObject* res;
if (!PyUnicode_Check(name)) {
PyErr_Format(PyExc_TypeError,
"attribute name must be string, not '%.200s'",
name->ob_type->tp_name);
return NULL;
}
/* Initialize this type (we'll assume the metatype is initialized) */
if (type->tp_dict == NULL) {
if (PyType_Ready(type) < 0)
return NULL;
}
/* No readable descriptor found yet */
meta_get = NULL;
/* Look for the attribute in the metatype */
// 在元類上尋找對(duì)應(yīng)的屬性
meta_attribute = _PyType_Lookup(metatype, name);
// 如果元類中存在該屬性
if (meta_attribute != NULL) {
Py_INCREF(meta_attribute);
// 獲取屬性的__get__方法
meta_get = Py_TYPE(meta_attribute)->tp_descr_get;
// 如果是數(shù)據(jù)描述符(__get__和__set__方法都有)
if (meta_get != NULL && PyDescr_IsData(meta_attribute)) {
/* Data descriptors implement tp_descr_set to intercept
* writes. Assume the attribute is not overridden in
* type's tp_dict (and bases): call the descriptor now.
*/
// 獲取執(zhí)行對(duì)應(yīng)__get__方法的返回值
res = meta_get(meta_attribute, (PyObject *)type,
(PyObject *)metatype);
Py_DECREF(meta_attribute);
return res;
}
}
/* No data descriptor found on metatype. Look in tp_dict of this
* type and its bases */
// 去mro列表中依次尋找__dict__中是否有對(duì)應(yīng)屬性
attribute = _PyType_Lookup(type, name);
// 如果屬性存在
if (attribute != NULL) {
/* Implement descriptor functionality, if any */
Py_INCREF(attribute);
descrgetfunc local_get = Py_TYPE(attribute)->tp_descr_get;
Py_XDECREF(meta_attribute);
// 如果是描述符碧信,則返回描述符的執(zhí)行結(jié)果
if (local_get != NULL) {
/* NULL 2nd argument indicates the descriptor was
* found on the target object itself (or a base) */
res = local_get(attribute, (PyObject *)NULL,
(PyObject *)type);
Py_DECREF(attribute);
return res;
}
// 否則直接返回屬性
return attribute;
}
/* No attribute found in local __dict__ (or bases): use the
* descriptor from the metatype, if any */
// 如果元類上對(duì)應(yīng)name是非數(shù)據(jù)描述符
if (meta_get != NULL) {
PyObject *res;
// 執(zhí)行對(duì)應(yīng)__get__方法獲取返回值
res = meta_get(meta_attribute, (PyObject *)type,
(PyObject *)metatype);
Py_DECREF(meta_attribute);
return res;
}
/* If an ordinary attribute was found on the metatype, return it now */
// 如果元類上對(duì)應(yīng)name如果不是描述符,則直接返回屬性
if (meta_attribute != NULL) {
return meta_attribute;
}
/* Give up */
PyErr_Format(PyExc_AttributeError,
"type object '%.50s' has no attribute '%U'",
type->tp_name, name);
return NULL;
}
載入屬性
在對(duì)象中調(diào)用屬性時(shí)街夭,將會(huì)執(zhí)行LOAD_ATTR
指令查找對(duì)應(yīng)的屬性(指令會(huì)調(diào)用PyObject_GetAttr
進(jìn)行查找)砰碴,因此會(huì)有一定的性能損耗,舉例:
from time import time
class A:
def run(self):
pass
l = 100000000
a = A()
def count_time(func):
def wrapper():
s = time()
func()
print(func.__name__, ":", time() - s)
wrapper()
@count_time
def get_attr_once():
"""只獲取一次方法屬性"""
ar = a.run
for i in range(l):
ar
@count_time
def get_attr_every():
"""每次都獲取一次方法屬性"""
for i in range(l):
a.run
# get_attr_once : 4.7732908725738525
# get_attr_every : 11.221587419509888
其中LOAD_ATTR
指令源碼如下:
// LOAD_ATTR操作
case TARGET(LOAD_ATTR): {
PyObject *name = GETITEM(names, oparg);
PyObject *owner = TOP();
// 查找對(duì)象屬性
PyObject *res = PyObject_GetAttr(owner, name);
Py_DECREF(owner);
SET_TOP(res);
if (res == NULL)
goto error;
DISPATCH();
}
...
// getattr邏輯
PyObject *
PyObject_GetAttr(PyObject *v, PyObject *name)
{
PyTypeObject *tp = Py_TYPE(v);
if (!PyUnicode_Check(name)) {
PyErr_Format(PyExc_TypeError,
"attribute name must be string, not '%.200s'",
name->ob_type->tp_name);
return NULL;
}
if (tp->tp_getattro != NULL)
return (*tp->tp_getattro)(v, name);
if (tp->tp_getattr != NULL) {
const char *name_str = PyUnicode_AsUTF8(name);
if (name_str == NULL)
return NULL;
// 通過對(duì)象的getattr方法查找屬性
return (*tp->tp_getattr)(v, (char *)name_str);
}
PyErr_Format(PyExc_AttributeError,
"'%.50s' object has no attribute '%U'",
tp->tp_name, name);
return NULL;
}
方法中self的由來
調(diào)用實(shí)例對(duì)象方法時(shí)我們明明沒有傳入self
板丽,那方法是如何獲取到其對(duì)應(yīng)的實(shí)例對(duì)象self
的呢呈枉?由于方法是一個(gè)method
類,而在method
類中維護(hù)著幾個(gè)重要的屬性:
__self__ 調(diào)用該方法的實(shí)例對(duì)象
__func__ 方法對(duì)應(yīng)的執(zhí)行函數(shù)
因此在調(diào)用實(shí)例方法時(shí)埃碱,method
類會(huì)調(diào)用其維護(hù)的__func__
函數(shù)猖辫,并將__self__
對(duì)象作為第一個(gè)參數(shù)傳入,舉例:
class A:
def run(self):
print(self)
def test():
a = A()
a.run()
# 上一句的本質(zhì)
a.run.__func__(a.run.__self__)
# 和上面的也等價(jià)
# 因?yàn)閍.run.__func__指向的就是A.run函數(shù)砚殿,而a.run.__self__指向的就是a
A.run(a)
test()
# <__main__.A object at 0x00000287114D4780>
# <__main__.A object at 0x00000287114D4780>
# <__main__.A object at 0x00000287114D4780>
而這個(gè)調(diào)用方式我們也可以在python源碼當(dāng)中得到證明:
// ~/Objects/classobject.c
// method對(duì)象定義
typedef struct {
PyObject_HEAD
// 綁定的函數(shù)
PyObject *im_func; /* The callable object implementing the method */
// 綁定的對(duì)象
PyObject *im_self; /* The instance it is bound to */
// 弱引用列表
PyObject *im_weakreflist; /* List of weak references */
// method的調(diào)用方式(獲取對(duì)應(yīng)的函數(shù)和對(duì)象啃憎,然后再進(jìn)行調(diào)用)
vectorcallfunc vectorcall;
} PyMethodObject;
// 創(chuàng)建一個(gè)方法類,會(huì)傳入綁定的函數(shù)和對(duì)象
PyObject *
PyMethod_New(PyObject *func, PyObject *self)
{
PyMethodObject *im;
if (self == NULL) {
PyErr_BadInternalCall();
return NULL;
}
// 緩沖池操作
im = free_list;
if (im != NULL) {
free_list = (PyMethodObject *)(im->im_self);
(void)PyObject_INIT(im, &PyMethod_Type);
numfree--;
}
else {
im = PyObject_GC_New(PyMethodObject, &PyMethod_Type);
if (im == NULL)
return NULL;
}
// 初始化設(shè)置
im->im_weakreflist = NULL;
Py_INCREF(func);
// 綁定的函數(shù)
im->im_func = func;
Py_XINCREF(self);
// 綁定的對(duì)象
im->im_self = self;
// 調(diào)用method類時(shí)的處理函數(shù)
im->vectorcall = method_vectorcall;
_PyObject_GC_TRACK(im);
return (PyObject *)im;
}
// method類的調(diào)用方式
static PyObject *
method_vectorcall(PyObject *method, PyObject *const *args,
size_t nargsf, PyObject *kwnames)
{
assert(Py_TYPE(method) == &PyMethod_Type);
PyObject *self, *func, *result;
// 獲取綁定的對(duì)象和函數(shù)
self = PyMethod_GET_SELF(method);
func = PyMethod_GET_FUNCTION(method);
// 獲取參數(shù)數(shù)量
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
// 如果有參數(shù)似炎,則將self插入到第一個(gè)參數(shù)前面
if (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET) {
/* PY_VECTORCALL_ARGUMENTS_OFFSET is set, so we are allowed to mutate the vector */
// 找到第一個(gè)參數(shù)的前一個(gè)位置空間
PyObject **newargs = (PyObject**)args - 1;
// 參數(shù)數(shù)量+1
nargs += 1;
// 將第一個(gè)位置的參數(shù)(實(shí)際上是原本第一個(gè)參數(shù)的上一個(gè)位置)設(shè)置為self
PyObject *tmp = newargs[0];
newargs[0] = self;
// 調(diào)用函數(shù)
result = _PyObject_Vectorcall(func, newargs, nargs, kwnames);
// 恢復(fù)原來的內(nèi)存布局
newargs[0] = tmp;
}
else {
// 鍵參數(shù)數(shù)量
Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
// 總的參數(shù)數(shù)量
Py_ssize_t totalargs = nargs + nkwargs;
// 沒有參數(shù)辛萍,則直接將self傳入
if (totalargs == 0) {
return _PyObject_Vectorcall(func, &self, 1, NULL);
}
// 有參數(shù)則申請(qǐng)空間來存放參數(shù)
PyObject *newargs_stack[_PY_FASTCALL_SMALL_STACK];
PyObject **newargs;
if (totalargs <= (Py_ssize_t)Py_ARRAY_LENGTH(newargs_stack) - 1) {
newargs = newargs_stack;
}
else {
newargs = PyMem_Malloc((totalargs+1) * sizeof(PyObject *));
if (newargs == NULL) {
PyErr_NoMemory();
return NULL;
}
}
/* use borrowed references */
// 依舊第一個(gè)位置的參數(shù)為self
newargs[0] = self;
/* bpo-37138: since totalargs > 0, it's impossible that args is NULL.
* We need this, since calling memcpy() with a NULL pointer is
* undefined behaviour. */
assert(args != NULL);
// 將剩下的參數(shù)放在第一個(gè)參數(shù)的后面
memcpy(newargs + 1, args, totalargs * sizeof(PyObject *));
result = _PyObject_Vectorcall(func, newargs, nargs+1, kwnames);
if (newargs != newargs_stack) {
PyMem_Free(newargs);
}
}
return result;
}
類方法/成員方法/靜態(tài)方法比較
類方法在創(chuàng)建完類之后就會(huì)將函數(shù)與當(dāng)前類進(jìn)行綁定,變成一個(gè)方法對(duì)象名党;成員方法則是在實(shí)例化時(shí)將函數(shù)與當(dāng)前對(duì)象進(jìn)行綁定叹阔,變成一個(gè)方法對(duì)象;而靜態(tài)方法則是一直都只是函數(shù)對(duì)象传睹,不綁定任何對(duì)象耳幢,舉例:
class A:
@classmethod
def xxx(cls):
pass
def yyy(self):
pass
@staticmethod
def zzz():
pass
print(type(A.xxx), A.xxx.__self__)
print(type(A.yyy))
print(type(A.zzz))
a = A()
print(type(a.xxx), a.xxx.__self__)
print(type(a.yyy), a.yyy.__self__)
print(type(a.zzz))
# <class 'method'> <class '__main__.A'>
# <class 'function'>
# <class 'function'>
# <class 'method'> <class '__main__.A'>
# <class 'method'> <__main__.A object at 0x00000186AE62D198>
# <class 'function'>
type獲取元類本質(zhì)
每個(gè)對(duì)象都有一個(gè)__class__
屬性用于記錄實(shí)例化當(dāng)前對(duì)象的類,而type
獲取類就是獲取的該屬性欧啤,證明如下:
class A: pass
class B: pass
a = A()
# 改變實(shí)例對(duì)象a指向的類
a.__class__ = B
# 可以看出type輸出的是B類
print(type(a), type(a) is a.__class__)
# <class '__main__.B'> True
而對(duì)于一個(gè)對(duì)象睛藻,其屬性的查找方式有一部分也是基于__class__
屬性來實(shí)現(xiàn)(先找到實(shí)例自身里查找,如果沒有邢隧,再通過__class__
指向的類的mro順序進(jìn)行查找)店印,證明如下:
class A:
def __init__(self):
self.x = 1
def run(self):
print(1)
class B: pass
a = A()
a.__class__ = B
# x是實(shí)例對(duì)象中的屬性,所以可以獲取到
print(a.x)
# run是A類里的屬性倒慧,因?yàn)橹赶虻念愖兂闪薆按摘,所以無法再找到run屬性了
a.run(1)
# 1
# AttributeError: 'B' object has no attribute 'run'
類對(duì)象和實(shí)例對(duì)象下的描述符
當(dāng)描述符作為類對(duì)象的屬性時(shí)包券,將會(huì)按照描述符的方式去解析,但放到實(shí)例對(duì)象當(dāng)中時(shí)炫贤,就會(huì)變成一個(gè)單純的對(duì)象溅固,證明如下:
class D:
def __init__(self):
self.x = 0
def __get__(self, i, o):
return self.x
def __set__(self, i, v):
self.x = v
class A:
x = D()
def __init__(self):
self.y = D()
a = A()
print(a.x, a.y, a.__dict__)
# 0 <__main__.D object at 0x000001E734A54A20> {'y': <__main__.D object at 0x000001E734A54A20>}
可以看到x
作為類屬性,返回的是通過__get__
方法獲取的值兰珍,而y
作為實(shí)例屬性侍郭,返回的只是一個(gè)對(duì)象
命名空間和作用域
搜索規(guī)則
命名空間和作用域參考:https://blog.csdn.net/qq_38329988/article/details/88667825
LGB規(guī)則
命名空間下查詢變量時(shí),遵循:local -> global -> builtin的順序掠河,即LGB規(guī)則(如果存在閉包亮元,則遵循LEGB規(guī)則,其中E是Enclosing)唠摹,舉例:
>>> list
<class 'list'>
# 查找到的是builtin里的list
>>> list = 1
# 在local定義list(全局區(qū)的local和global指向的是同一個(gè))
>>> list
1
# 獲取的是local的list
>>> del list
# 刪除的是local的list
>>> list
<class 'list'>
# 可以看到再次獲取到了builtin中的list
>>> __builtins__.list
<class 'list'>
>>> __builtins__.list = 1
# 修改builtin中的list爆捞,會(huì)導(dǎo)致很多內(nèi)部對(duì)list的使用出問題,程序崩潰
>>> list
1
Exception ignored in:
...
命名操作規(guī)則
對(duì)命名進(jìn)行的操作如果會(huì)影響命名空間發(fā)生改變勾拉,那么操作將作用于局部作用域嵌削,例如賦值操作、刪除操作等望艺,舉例:
>>> globals() is locals()
True
# 模塊下函數(shù)外的全局作用域和局部作用域指向的是同一個(gè)
>>> del list
NameError: name 'list' is not defined
# del操作會(huì)觸發(fā)命名空間改變,因此對(duì)局部作用域進(jìn)行操作
# 因?yàn)榫植孔饔糜蛳聸]有l(wèi)ist肌访,所以刪除失斦夷(list在內(nèi)置作用域)
>>> a = []
>>> def test():
del a
>>> test()
UnboundLocalError: local variable 'a' referenced before assignment
# 對(duì)命名進(jìn)行操作,因此只查找test函數(shù)下的局部變量吼驶,顯然a不存在(a在全局作用域)
>>> a.append(1)
>>> def test():
del a[0]
>>> test()
# 對(duì)引用類型內(nèi)部進(jìn)行操作惩激,命名空間不產(chǎn)生變化,因此操作成功
>>> a
[]
再比如下面的例子:
a = 100
def test1():
print(a)
def test2():
print(a)
a = 1
test1()
test2()
# 100
# UnboundLocalError: local variable 'a' referenced before assignment
可以看到同樣是輸出a
蟹演,但test2
卻報(bào)錯(cuò)提示a
沒有定義风钻,這就是因?yàn)?code>test2中存在對(duì)a
的賦值語句,因此解析時(shí)認(rèn)為a
是存在于test2
局部的酒请,從而導(dǎo)致錯(cuò)誤的發(fā)生骡技。如果希望能夠使用全局作用域上的a
,那么可以加globals
關(guān)鍵字羞反,代表強(qiáng)制對(duì)全局中的a
進(jìn)行操作布朦,而不用遵守LGB
規(guī)則
再比如下面示例:
# other.py代碼:
# a = 1
# def test():
# print(a)
from other import a, test
# 雖然導(dǎo)入了a,但a=100會(huì)對(duì)當(dāng)前命名空間產(chǎn)生影響昼窗,所以操作的是當(dāng)前命名空間下的局部變量是趴,而不是模塊下的a變量
a = 100
test()
import other
# 修改的是模塊下的屬性,對(duì)當(dāng)前命名空間不會(huì)產(chǎn)生影響
other.a = 1000
test()
# 1
# 1000
全局區(qū)的local/global關(guān)系
由于全局區(qū)存放的變量都是全局的澄惊,也就不存在局部變量一說唆途,因此在全局區(qū)里富雅,locals()
和globals()
指向的是同一個(gè)對(duì)象,證明如下:
>>> globals() is locals()
True
locals()原理
局部變量存儲(chǔ)在當(dāng)前函數(shù)執(zhí)行環(huán)境的椄匕幔空間里没佑,因此在對(duì)應(yīng)的棧幀對(duì)象中,有一個(gè)f_locals
屬性會(huì)指向當(dāng)前棧幀里的局部變量字典滚婉,而locals
函數(shù)的本質(zhì)也就是從當(dāng)前棧幀當(dāng)中獲取f_locals
屬性图筹,證明如下:
import sys
def test():
f = sys._getframe()
print(locals() is f.f_locals)
test()
# True
源碼邏輯如下:
// locals邏輯
PyObject *
PyEval_GetLocals(void)
{
// 獲取當(dāng)前執(zhí)行的線程對(duì)象
PyThreadState *tstate = _PyThreadState_GET();
// 獲取當(dāng)前執(zhí)行的棧幀對(duì)象
PyFrameObject *current_frame = _PyEval_GetFrame(tstate);
if (current_frame == NULL) {
_PyErr_SetString(tstate, PyExc_SystemError, "frame does not exist");
return NULL;
}
if (PyFrame_FastToLocalsWithError(current_frame) < 0) {
return NULL;
}
assert(current_frame->f_locals != NULL);
// 返回棧幀指向的f_locals
return current_frame->f_locals;
}
同理,函數(shù)執(zhí)行時(shí)對(duì)應(yīng)的棧幀對(duì)象中也會(huì)將全局變量的指向存放到f_globals
屬性里让腹,因此globals()
函數(shù)也就是獲取當(dāng)前棧幀的f_globals
屬性
模塊
import機(jī)制
import
時(shí)會(huì)先判斷sys.modules
中是否存在該模塊远剩,如果存在則直接將該模塊返回,否則從sys.path
的路徑當(dāng)中尋找對(duì)應(yīng)的模塊進(jìn)行導(dǎo)入骇窍,舉例:
import sys
import random
m1 = random
import random
print(m1 is random)
# True
可以看出第二次導(dǎo)入的模塊和第一次導(dǎo)入的是同一個(gè)瓜晤。所以如果希望重新載入一次模塊,那么可以將sys.module
的對(duì)應(yīng)模塊刪除腹纳,從而重新導(dǎo)入(但不代表之前的模塊資源會(huì)被釋放痢掠,例如下面的示例中變量m1
引用了之前導(dǎo)入的模塊,那么引用不為0嘲恍,不會(huì)被釋放足画,所以需要注意內(nèi)存泄露的問題),舉例:
import sys
import random
m1 = random
# 刪除模塊佃牛,使其能夠重新導(dǎo)入
del sys.modules["random"]
import random
print(m1 is random)
# False
線程
GIL機(jī)制
python中所有的多線程實(shí)際上只能并發(fā)執(zhí)行淹辞,即同一時(shí)間里,只能有一個(gè)線程在真正的運(yùn)行俘侠,而之所以這樣象缀,就是因?yàn)閜ython的多線程當(dāng)中有一把大鎖gil,用來控制當(dāng)前允許執(zhí)行的線程爷速。
為什么要有g(shù)il:由于python的內(nèi)存管理主要是靠引用計(jì)數(shù)來完成了央星,為了避免資源的使用異常(計(jì)數(shù)期間,如果調(diào)度到別的線程惫东,則很可能引發(fā)一些意外的情況莉给,如:A線程使用的資源在B線程被釋放、資源應(yīng)該釋放時(shí)卻沒有被正確回收)凿蒜,所以通過gil限制了不允許多個(gè)線程同時(shí)執(zhí)行
既然是為了保證引用計(jì)數(shù)的正確禁谦,為什么gil不只對(duì)引用計(jì)數(shù)的部分進(jìn)行上鎖:首先在源碼當(dāng)中,引用計(jì)數(shù)的代碼特別多废封,只對(duì)該部分進(jìn)行g(shù)il控制州泊,那么代碼將可能變得難以維護(hù),并且上鎖和釋放鎖是十分消耗資源的漂洋,頻繁地進(jìn)行這些操作遥皂,反而會(huì)降低性能
gil控制線程切換方式:主要通過
_PyThreadState_Swap
方法更新當(dāng)前占用gil的線程力喷,源碼如下:
// 更新當(dāng)前獲得GIL鎖的線程(切換線程)
PyThreadState *
_PyThreadState_Swap(struct _gilstate_runtime_state *gilstate, PyThreadState *newts)
{
// 當(dāng)前獲取gil的線程
PyThreadState *oldts = _PyRuntimeGILState_GetThreadState(gilstate);
// 設(shè)置新的獲取GIL的線程
_PyRuntimeGILState_SetThreadState(gilstate, newts);
/* It should not be possible for more than one thread state
to be used for a thread. Check this the best we can in debug
builds.
*/
#if defined(Py_DEBUG)
if (newts) {
/* This can be called from PyEval_RestoreThread(). Similar
to it, we need to ensure errno doesn't change.
*/
int err = errno;
PyThreadState *check = _PyGILState_GetThisThreadState(gilstate);
if (check && check->interp == newts->interp && check != newts)
Py_FatalError("Invalid thread state for this thread");
errno = err;
}
#endif
return oldts;
}
調(diào)度切換機(jī)制
Python在進(jìn)行多線程編程時(shí),會(huì)維護(hù)一個(gè)時(shí)間片數(shù)值N演训,當(dāng)執(zhí)行了指定長的時(shí)間片后就會(huì)進(jìn)行線程的切換弟孟,我們可以通過sys
模塊下getswitchinterval
/setswitchinterval
方法來進(jìn)行查看和設(shè)置(python2中是維護(hù)執(zhí)行指令的個(gè)數(shù),即根據(jù)執(zhí)行指令個(gè)數(shù)來進(jìn)行線程切換样悟,使用的是getcheckinterval
/setcheckinterval
方法來進(jìn)行查看和設(shè)置)拂募,舉例:
>>> import sys
>>> sys.getswitchinterval()
0.005
# 默認(rèn)是5毫秒
>>> sys.setswitchinterval(1)
>>> sys.getswitchinterval()
1.0
例如下面的代碼,我們就可以通過修改時(shí)間片來查看運(yùn)行的結(jié)果:
import threading
import sys
# 可以對(duì)比執(zhí)行這句和不執(zhí)行這句的差別(默認(rèn)0.005秒窟她,改為1秒)
# sys.setswitchinterval(1)
total = 0
li = []
def test(n):
global total
print(n, "s")
for i in range(100000):
total += 1
print(n, "e")
for i in range(3):
t = threading.Thread(target=test, args=(i,))
t.start()
li.append(t)
[t.join() for t in li]
print(total)
相關(guān)接口源碼如下(知道代碼所在文件就行):
// ~/Python/sysmodule.c
// 設(shè)置線程調(diào)度切換基準(zhǔn)
static PyObject *
sys_setswitchinterval_impl(PyObject *module, double interval)
/*[clinic end generated code: output=65a19629e5153983 input=561b477134df91d9]*/
{
if (interval <= 0.0) {
PyErr_SetString(PyExc_ValueError,
"switch interval must be strictly positive");
return NULL;
}
_PyEval_SetSwitchInterval((unsigned long) (1e6 * interval));
Py_RETURN_NONE;
}
static double
sys_getswitchinterval_impl(PyObject *module)
/*[clinic end generated code: output=a38c277c85b5096d input=bdf9d39c0ebbbb6f]*/
{
return 1e-6 * _PyEval_GetSwitchInterval();
}
// ~/Python/ceval_gil.h
void _PyEval_SetSwitchInterval(unsigned long microseconds)
{
_PyRuntime.ceval.gil.interval = microseconds;
}
// 默認(rèn)5000微秒陈症,即5毫秒
#define DEFAULT_INTERVAL 5000
// 初始化gil時(shí),設(shè)置時(shí)間片
static void _gil_initialize(struct _gil_runtime_state *gil)
{
_Py_atomic_int uninitialized = {-1};
gil->locked = uninitialized;
gil->interval = DEFAULT_INTERVAL;
}
GC機(jī)制
引用計(jì)數(shù)
python中大部分情況下垃圾回收是靠引用計(jì)數(shù)來進(jìn)行管理的震糖,所以在源碼當(dāng)中會(huì)看到大量的增加和減少計(jì)數(shù)操作录肯,其中對(duì)于不同類型的數(shù)據(jù),引用計(jì)數(shù)的邏輯可能也會(huì)進(jìn)行相關(guān)的封裝吊说,這里列舉了最基本的引用計(jì)數(shù)源碼邏輯:
// 計(jì)數(shù)+1
static inline void _Py_INCREF(PyObject *op)
{
_Py_INC_REFTOTAL;
// 對(duì)計(jì)數(shù)屬性+1
op->ob_refcnt++;
}
// _PyObject_CAST源碼:
// #define _PyObject_CAST(op) ((PyObject*)(op))
// 就是將對(duì)象強(qiáng)轉(zhuǎn)成PyObject類型
#define Py_INCREF(op) _Py_INCREF(_PyObject_CAST(op))
// 計(jì)數(shù)-1
static inline void _Py_DECREF(const char *filename, int lineno,
PyObject *op)
{
(void)filename; /* may be unused, shut up -Wunused-parameter */
(void)lineno; /* may be unused, shut up -Wunused-parameter */
_Py_DEC_REFTOTAL;
// 對(duì)計(jì)數(shù)-1论咏,并判斷如果引用為0,則回收
if (--op->ob_refcnt != 0) {
// debug下相關(guān)操作
#ifdef Py_REF_DEBUG
if (op->ob_refcnt < 0) {
_Py_NegativeRefcount(filename, lineno, op);
}
#endif
}
else {
// 回收操作
_Py_Dealloc(op);
}
}
#define Py_DECREF(op) _Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op))
標(biāo)記清除
gc管理對(duì)象
標(biāo)記清除只會(huì)對(duì)gc管理的對(duì)象類型進(jìn)行標(biāo)記和監(jiān)控颁井,然后將其中不可達(dá)的對(duì)象清除厅贪。而對(duì)于gc管理的類型,會(huì)在定義時(shí)雅宾,在tp_flags
屬性中進(jìn)行設(shè)置卦溢,例如list
類型的tp_flags
定義如下:
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_LIST_SUBCLASS
// Py_TPFLAGS_HAVE_GC就是gc管理對(duì)象的定義,宏定義如下:
// #define Py_TPFLAGS_HAVE_GC (1UL << 14)
可以通過類對(duì)象的__flags__
屬性查看秀又,第15位如果為1,則為gc管理對(duì)象贬芥,下面封裝了函數(shù)來判斷是否為gc管理對(duì)象:
def is_gc_type(t):
return bin(t.__flags__)[-15] == "1"
print(is_gc_type(int))
print(is_gc_type(float))
print(is_gc_type(bool))
print(is_gc_type(str))
print(is_gc_type(list))
print(is_gc_type(set))
print(is_gc_type(dict))
# False
# False
# False
# False
# True
# True
# True
而對(duì)象gc管理的對(duì)象類型吐辙,都會(huì)通過PyObject_GC_New
方法來創(chuàng)建對(duì)象,例如dict
對(duì)象的創(chuàng)建源碼:
// 創(chuàng)建一個(gè)dict對(duì)象
static PyObject *
new_dict(PyDictKeysObject *keys, PyObject **values)
{
PyDictObject *mp;
assert(keys != NULL);
// 緩存池操作
if (numfree) {
// ...
}
else {
// 通過PyObject_GC_New創(chuàng)建gc管理的對(duì)象
mp = PyObject_GC_New(PyDictObject, &PyDict_Type);
// ...
}
// ...
}
并且使用PyObject_GC_New
創(chuàng)建的對(duì)象蘸劈,則會(huì)被添加到gc第0代的鏈表當(dāng)中昏苏,源碼如下:
#define PyObject_GC_New(type, typeobj) \
( (type *) _PyObject_GC_New(typeobj) )
PyObject *
_PyObject_GC_New(PyTypeObject *tp)
{
PyObject *op = _PyObject_GC_Malloc(_PyObject_SIZE(tp));
if (op != NULL)
op = PyObject_INIT(op, tp);
return op;
}
PyObject *
_PyObject_GC_Malloc(size_t basicsize)
{
return _PyObject_GC_Alloc(0, basicsize);
}
// 創(chuàng)建一個(gè)gc管理對(duì)象的實(shí)現(xiàn)
static PyObject *
_PyObject_GC_Alloc(int use_calloc, size_t basicsize)
{
struct _gc_runtime_state *state = &_PyRuntime.gc;
PyObject *op;
PyGC_Head *g;
size_t size;
if (basicsize > PY_SSIZE_T_MAX - sizeof(PyGC_Head))
return PyErr_NoMemory();
size = sizeof(PyGC_Head) + basicsize;
// 分配創(chuàng)建對(duì)象的內(nèi)存
if (use_calloc)
g = (PyGC_Head *)PyObject_Calloc(1, size);
else
g = (PyGC_Head *)PyObject_Malloc(size);
if (g == NULL)
return PyErr_NoMemory();
assert(((uintptr_t)g & 3) == 0); // g must be aligned 4bytes boundary
g->_gc_next = 0;
g->_gc_prev = 0;
// 第0代數(shù)量+1
state->generations[0].count++; /* number of allocated GC objects */
// 如果達(dá)到第0代回收條件則進(jìn)行回收
if (state->generations[0].count > state->generations[0].threshold &&
state->enabled &&
state->generations[0].threshold &&
!state->collecting &&
!PyErr_Occurred()) {
state->collecting = 1;
collect_generations(state);
state->collecting = 0;
}
// 添加到gc管理的鏈表
op = FROM_GC(g);
return op;
}
當(dāng)進(jìn)行標(biāo)記清除時(shí),則會(huì)遍歷每一代的頭結(jié)點(diǎn)威沫,將不可達(dá)的進(jìn)行回收
自定義gc對(duì)象類型
在創(chuàng)建類對(duì)象時(shí)贤惯,type
會(huì)根據(jù)類的尺寸、繼承的基類等來判斷是否為需要gc管理的類型棒掠,例如將類的__slots__
屬性設(shè)為空元組孵构,那么創(chuàng)建的對(duì)象就不會(huì)有自定義的屬性存在,所以也就不用gc來管理了(gc只管理那些可能造成循環(huán)引用的對(duì)象)烟很,源碼如下:
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
{
// ...
// 如果父類是gc管理的颈墅,或者當(dāng)前類的尺寸比父類大(還有父類以外的屬性)蜡镶,則需要進(jìn)行g(shù)c管理
if ((base->tp_flags & Py_TPFLAGS_HAVE_GC) ||
type->tp_basicsize > base->tp_basicsize)
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
// ...
}
而type
創(chuàng)建對(duì)象時(shí),也會(huì)結(jié)合是否為gc管理對(duì)象來選擇創(chuàng)建對(duì)象的方式恤筛,源碼如下:
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
{
// ...
// type創(chuàng)建對(duì)象的方法
type->tp_alloc = PyType_GenericAlloc;
// ...
}
PyObject *
PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)
{
PyObject *obj;
const size_t size = _PyObject_VAR_SIZE(type, nitems+1);
// 對(duì)于gc管理的對(duì)象則用對(duì)應(yīng)的方式創(chuàng)建對(duì)象
if (PyType_IS_GC(type))
obj = _PyObject_GC_Malloc(size);
else
obj = (PyObject *)PyObject_MALLOC(size);
// ...
return obj;
}
示例如下:
def is_gc_type(t):
return bin(t.__flags__)[-15] == "1"
class A:
pass
class B:
__slots__ = ()
class C(A):
__slots__ = ()
print(is_gc_type(A))
print(is_gc_type(B))
print(is_gc_type(C))
# True
# False
# True
分代回收
從前面的標(biāo)記清除可以發(fā)現(xiàn):當(dāng)創(chuàng)建gc管理的對(duì)象時(shí)官还,會(huì)進(jìn)行分代回收閾值條件的判斷,當(dāng)符合條件時(shí)毒坛,就會(huì)自動(dòng)進(jìn)行垃圾回收望伦,源碼如下:
// 分代回收邏輯
static Py_ssize_t
collect_generations(struct _gc_runtime_state *state)
{
/* Find the oldest generation (highest numbered) where the count
* exceeds the threshold. Objects in the that generation and
* generations younger than it will be collected. */
Py_ssize_t n = 0;
for (int i = NUM_GENERATIONS-1; i >= 0; i--) {
// 當(dāng)前代的計(jì)數(shù)大于設(shè)置閾值(第一代的計(jì)數(shù)和二三代的意思不同),才能進(jìn)行回收操作
if (state->generations[i].count > state->generations[i].threshold) {
/* Avoid quadratic performance degradation in number
of tracked objects. See comments at the beginning
of this file, and issue #4074.
*/
if (i == NUM_GENERATIONS - 1
&& state->long_lived_pending < state->long_lived_total / 4)
continue;
// 調(diào)用回收函數(shù)煎殷,并返回回收的對(duì)象數(shù)量
n = collect_with_callback(state, i);
// 只會(huì)對(duì)第一個(gè)符合情況的代進(jìn)行回收
break;
}
}
return n;
}
// 垃圾回收函數(shù)
static Py_ssize_t
collect_with_callback(struct _gc_runtime_state *state, int generation)
{
assert(!PyErr_Occurred());
Py_ssize_t result, collected, uncollectable;
// 垃圾回收開始的回調(diào)
invoke_gc_callback(state, "start", generation, 0, 0);
// 真正回收的實(shí)現(xiàn)
result = collect(state, generation, &collected, &uncollectable, 0);
// 垃圾回收結(jié)束回調(diào)
invoke_gc_callback(state, "stop", generation, collected, uncollectable);
assert(!PyErr_Occurred());
return result;
}
// 回收主邏輯
static Py_ssize_t
collect(struct _gc_runtime_state *state, int generation,
Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable, int nofail)
{
// ...
// 下一代的計(jì)數(shù)+1
if (generation+1 < NUM_GENERATIONS)
state->generations[generation+1].count += 1;
// 清空當(dāng)前代及更年輕代的計(jì)數(shù)
for (i = 0; i <= generation; i++)
state->generations[i].count = 0;
// ...
// 尋找和刪除不可達(dá)對(duì)象
gc_list_init(&unreachable);
move_unreachable(young, &unreachable);
// ...
// 統(tǒng)計(jì)并更新回收屯伞、未回收對(duì)象的數(shù)量
if (n_collected) {
*n_collected = m;
}
if (n_uncollectable) {
*n_uncollectable = n;
}
struct gc_generation_stats *stats = &state->generation_stats[generation];
stats->collections++;
stats->collected += m;
stats->uncollectable += n;
// ...
return n+m;
}
或者我們也可以通過gc.collect
接口來手動(dòng)調(diào)用垃圾回收,源碼如下:
/* API to invoke gc.collect() from C */
// 垃圾回收接口
Py_ssize_t
PyGC_Collect(void)
{
struct _gc_runtime_state *state = &_PyRuntime.gc;
if (!state->enabled) {
return 0;
}
Py_ssize_t n;
if (state->collecting) {
/* already collecting, don't do anything */
n = 0;
}
else {
PyObject *exc, *value, *tb;
// 設(shè)置開始回收標(biāo)志
state->collecting = 1;
PyErr_Fetch(&exc, &value, &tb);
// 調(diào)用垃圾回收
n = collect_with_callback(state, NUM_GENERATIONS - 1);
PyErr_Restore(exc, value, tb);
state->collecting = 0;
}
return n;
}
手動(dòng)調(diào)用垃圾回收示例如下:
import gc
# 先清空一次蝌数,避免影響結(jié)果
gc.collect()
# 添加gc回調(diào)
gc.callbacks.append(lambda flag, status: print(flag, status))
a = [None]
b = [a]
a[0] = b
print("start del...")
del a, b
print("end del...")
# 這里沒有達(dá)到分代回收的閾值愕掏,所以手動(dòng)調(diào)用垃圾回收
print("collect num:", gc.collect())
print("end")
# start del...
# end del...
# start {'generation': 2, 'collected': 0, 'uncollectable': 0}
# stop {'generation': 2, 'collected': 2, 'uncollectable': 0}
# collect num: 2
# end
# start {'generation': 2, 'collected': 0, 'uncollectable': 0}
# stop {'generation': 2, 'collected': 0, 'uncollectable': 0}
待更新...