直譯器篇
python的虛擬機器其實本質上就是模擬cpu執行過程,對應函式棧幀實現Python位元組碼的執行。
當虛擬機器真正執行的時候,其面對的不是PyCodeObject而是P yPyFrameObject
typedef struct _frame{
PyObject_VAR_HEAD
struct _frame *f_back;
PyCodeObject *f_code;
PyObject *f_builtins;
PyObject *f_globals;
PyObject *f_locals;
PyObject **f_valuestack;
PyObject **f_stacktop;
....
int f_lasti;
int f_lineno;
....
//動態記憶體,維護(區域性變數+cell物件集合+free物件集合+執行時棧)所需要的空間
PyObject *f_localsplus[1];
}
複製程式碼
執行時候的狀態
建立PyFrameObject過程
PyFrameObject *PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals, PyObject *locals){
PyFrameObject *f;
Py_ssize_t extras, ncells, nfree, i;
ncells = PyTuple_GET_SIZE(code->co_cellvals);
nfrees = PyTuple_GET_SIZE(code->co_freevars);
extras = code->code_stack + code->co_nlocals + ncells + nfrees;
f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, extras);
extras = code->con_nlocals + ncells + nfrees;
f->f_valuestack = f->f_localsplus + extras;
f->f_stacktop = f->f_valuestack;
return f;
}
複製程式碼
作用域
LEGB
一般分為兩種引用方式,屬性引用和名字引用,屬性引用本質是到名字引用空間中查詢一個名字所引用的物件。
直譯器執行位元組碼核心程式碼
PyObject *PyEval_EvalFrameEx(PyFrameObject *f, int throwflag){
...
why = WHY_NOT;
...
for(;;){
...
fast_next_opcode:
f->f_lasti = INSTR_OFFSET(); //類似PC暫存器的作用
opcode = NEXTOP();
oparg = 0;
if(HAS_ARG(opcode))
oparg = NEXTARG();
dispatch_opcode:
switch(opcode){
case NOP:
goto fast_next_opcode;
case LOAD_FAST:
...
}
}
}
why表明一個位元組碼指令執行結果。
enum Why_Code {
WHY_NOT = 0x0001
WHY_EXCEPTION = 0x0002
WHY_RERAISE = 0x0004 //在finally中觸發異常
WHY_RETURN = 0x0008
WHY_BREAK = 0x0010
WHY_CONTINUE = 0x0020
WHY_YIELD = 0x0040
}
複製程式碼
執行緒程式概念
程式在執行模式分為程式或者執行緒。這裡就會引入一個概念,PyFrameObject本質上是屬於某程式或者執行緒(當然最終肯定是與執行緒關聯)。程式是一個資源的管理單元,執行緒是一個程式最小執行單元。多個執行緒共享程式地址空間的全域性資訊。
Cpu切換任務的時候必須儲存執行緒的執行環境,類似作業系統程式排程。在python中一個執行緒擁有一個PyThreadState物件,而PyInterpreterState物件代表程式狀態物件。
為了節省記憶體,cpython在實現中讓多個執行緒共享相同的Module物件。一般來說一個python程式擁有一個interpreter物件,一個interpreter物件維護多個PyThreadState,多個Pythreadstate輪流使用位元組碼直譯器。
typedef struct _is {
struct _is *next;
struct _ts *tstate_head;
PyObject *modules;
PyObject *sysdict;
PyObject *builtins;
...
} PyInterpreterState
typedef struct _ts {
struct _ts *next;
PyInterpreterState *interp;
struct _frame *frame;
int recursion_depth;
...
PyObject *dict;
...
long thread_id;
} PyThreadState;
複製程式碼
當python虛擬機器開始執行的時候,會從frame中取出PyFrameObject設定為當前執行環境,建立新的棧幀則重新設定關係。
位元組碼與PyFrameObject
比如我們經常用到賦值語句: i = 1,其對應的位元組碼為:
0 LOAD_CONST 0 (1)
3 STORE_NAME 0 (i)
+對應的位元組碼
BINARY_ADD
w = POP();
v = TOP();
if(PyInt_CheckExact(v) && PyInt_CheckExact(w)){
register long a, b, i;
a = PyInt_AS_LONG(v);
b = PyInt_AS_LONG(w);
i = a+b;
if((i^a) <0 && (i^b)<0)
goto slow_add;
x = PyInt_FromLong(i);
}
else if (PyString_CheckExact(v) && PyString_CheckExact(w)){
x = string_concatenate(v, w, f, next_instr);
goto skip_decref_vx;
}
else{
slow_add:
x = PyNumber_Add(v, w);
}
Py_DECREF(v);
skip_decref_vx:
Py_DECREF(w);
SET_TOP(x);
break;
複製程式碼
邏輯語句的實現
if語句的邏輯比較簡單,類似彙編指令裡面的jump系列的命令,根據絕對地址或者相對地址找到下一條位元組碼指令。
for語句會涉及一個特別的位元組碼SETUP_LOOP
typedef struct _frame{
....
int f_iblock;
PyTryBlock f_blockstack[CO_MAXBLOCKS];
}
void PyFrame_BlockSetup(PyFrameObject *f, int type, int handler, int level){
PyTryBlock *b;
b = &f->f_blockstack[f->f_iblock++];
b->b_type = type;
b->b_level = level;
b->b_handler = handler
}
複製程式碼
函式物件
函式物件是在執行位元組碼的過程生成出來,其本質是包含了作用域和code物件的一個結構。
typedef struct {
PyObject_HEAD
PyObject *func_code;
PyObject *func_globals;
PyObject *func_defaults;
PyObject *func_closure;
PyObject *func_doc;
PyObject *func_name;
PyObject *func_dict;
PyObject *func_weakreflist;
PyObject *func_module;
} PyFunctionObj
複製程式碼
PyFunctionObject是python程式碼在執行時動態產生的,靜態資訊儲存在func_code之中。 對於一段程式碼而言,PyCodeObject物件只有一個,但PyFunctionObject可以有多個。
載入const中的code物件,然後建立一個function物件MAKE_FUNCTION邏輯
PyObject* PyFunction_New(PyObject *code, PyObject *globals){
PyFunctionObject *op = PyObject_GC_New(PyFunctionObject, &PyFunction_Type);
static PyObject *__name__ = 0;
if(op!=NULL){
...
op->func_code = code;
op->func_globals = globals;
op->func_name = ((PyCodeObject *)code)->co_name;
consts = ((PyCodeObject *)code)->co_consts;
if(PyTuple_size(consts)>=1) {
doc = PyTuple_GetItem(consts, 0);
if(!PyString_Check(doc) && !PyUnicode_Check(doc))
doc = Py_None
}
else
doc = Py_None
...
}
...
}
}
不論CFunction和Method呼叫都會進入這個call_function
static PyObject* call_function(PyObject ***pp_stack, int oparg){
int na = oparg & 0xff;
int nk = (oparg>>8) & 0xff;
int n = na + 2 * nk; //需要讀取的引數個數
PyObject **pfunc = (*pp_stack) -n -1;
PyObject *func = *pfunc;
PyObject *x, *w;
if(PyCFunction_Check(func) && nk == 0){
...
} else {
if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL){
...
}
if(PyFunction_Check(func))
x = fast_function(func, pp_stack, n, na, nk);
else
x = do_call(func, pp_stack, na, nk);
...
}
}
...
return x;
fast_function裡面的過程大致就是建立棧幀,然後從ThreadState中取出棧幀新增並呼叫執行新的棧幀
而且新棧幀的globals來源於之前棧幀的f_globals以及f_locals
函式的區域性變數是存放在PyFrameObject的 *f_localsplus對應的空間裡面,通過load_fast和store_fast進行操作
複製程式碼
建立一個類A對應的位元組碼
class A(object):
def __init__(self):
c = 10
print c
def b(self):
print "22"
複製程式碼
類以及型別
在python中每個物件都有一個type物件,可以通過物件的__class__獲取,對於instance的type對應是class物件,而對於class物件的type對應的就是metaclass物件。這個物件在python中對應的就是PyType_Type。
python中每個class物件直接或者間接會與object物件存在is_kind_of關係,type object對應的是PyObject_Type
對於int型別,其找到+對應的方法如下所示:
從PyInt_Type中找到符號表對應的__add__,對應找到函式簇的nb_add。
<type 'type'>對應是的PyType_Type
class ==> PyTypeObject
建立類的過程,為class設定基類關係,然後為其設定型別type,然後初始化dict內容
int PyType_Ready(PyTypeObject *type)
{
PyObject *dict, *bases;
PyTypeObject *base;
Py_ssize_t i, n;
//假設父類不存在以及非PyBaseObject_Type。所以type的父類為object
base = type->tp_base;
if(base == NULL && type != &PyBaseObject_Type){
base = type->tp_base = &PyBaseObject_Type;
}
//該條件判斷物件是否初始化完畢
if(base && base->tp_dict == NULL){
PyType_Ready(base)
}
//拷貝父類的型別物件賦值
if (type->ob_type == NULL && base != NULL){
type->ob_type = base->ob_type;
......
}
}
複製程式碼
這一步是進行tp_dict的填充
為pylist_Type填充__getitem__
建立類的位元組碼分析
比如某個類的定義如下
class A(object):
def __init__(self):
pass
def f(self):
pass
1 0 LOAD_CONST 0 ('A')
3 LOAD_NAME 0 (object)
6 BUILD_TUPLE 1
9 LOAD_CONST 1 (<code object A at 0x1073f73b0, file "test.py", line 1>)
12 MAKE_FUNCTION 0
15 CALL_FUNCTION 0
18 BUILD_CLASS
19 STORE_NAME 1 (A)
22 LOAD_CONST 2 (None)
25 RETURN_VALUE
複製程式碼
大致的邏輯是載入A,object建立關係繼承列表,載入code物件,建立一個function,然後呼叫function。function對應的內容其實就是函式code的內容。
python引擎在執行call_function其實對應是執行兩次def函式,建立兩個FunctionObject物件。
之後一個類的建立條件則滿足了,呼叫build_class建立一個class物件 build_class邏輯PyObject * build_class(PyObject *methods, PyObject *bases, PyObject *name){
PyObject *metaclass = NULL, *result, *base;
if(PyDict_check(methods))
metaclass = PyDict_GetItemString(methods, "__metaclass__");
if (metaclass != NULL)
Py_INCRE(metaclass);
else if (PyTuple_checks(bases) && PyTuple_Get_Size(bases)>0){
base = PyTuple_Get_ITEM(bases, 0);
metaclass = PyObject_GetAttrString(bases, "__class__");
}else{
....
}
result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods, NULL);
.....
return result;
}
檢視元類是否定義,沒有則從父類第一個提取出__class__作為metaclass。
複製程式碼
類和instance的__new__的差別
python的instance和class都擁有__dict__,因此不同物件可以設定不同的內容,不同的calss可以設定不同的內容。