大师兄的Python源码学习笔记(十八): 虚拟机中的控制流(五)
大师兄的Python源码学习笔记(二十): 虚拟机中的函数机制(二)
一、关于PyFunctionObject对象
- 在Python中,PyCodeObject对象是一段Python源代码的静态表示,在源代码经过编译后,一个Code Block会产生一个且只有一个PyCodeObject,它包含了这个Code Block的静态信息。
- 而PyFunctionObject对象则是在Python代码运行时动态产生的(执行def语句时)。
- 在PyFunctionObject对象中会包含这个函数的静态信息(*func_code中),此外还会包含一些函数在执行时必须的动态信息(上下文信息)。
Include\funcobject.h
typedef struct {
PyObject_HEAD
PyObject *func_code; /* A code object, the __code__ attribute */
PyObject *func_globals; /* A dictionary (other mappings won't do) */
PyObject *func_defaults; /* NULL or a tuple */
PyObject *func_kwdefaults; /* NULL or a dict */
PyObject *func_closure; /* NULL or a tuple of cell objects */
PyObject *func_doc; /* The __doc__ attribute, can be anything */
PyObject *func_name; /* The __name__ attribute, a string object */
PyObject *func_dict; /* The __dict__ attribute, a dict or NULL */
PyObject *func_weakreflist; /* List of weak references */
PyObject *func_module; /* The __module__ attribute, can be anything */
PyObject *func_annotations; /* Annotations, a dict or NULL */
PyObject *func_qualname; /* The qualified name */
/* Invariant:
* func_closure contains the bindings for func_code->co_freevars, so
* PyTuple_Size(func_closure) == PyCode_GetNumFree(func_code)
* (func_closure may be NULL if PyCode_GetNumFree(func_code) == 0).
*/
} PyFunctionObject;
成员 | 说明 |
---|---|
*func_code | 对应函数编译后的PyCodeObject对象 |
*func_globals | 函数运行时的global名字空间 |
*func_defaults | 默认参数(tuple) |
*func_kwdefaults | 默认参数(dict) |
*func_closure | 用于实现闭包的cell对象元祖 |
*func_doc | 函数的文档 |
*func_name | 函数名(__name__属性) |
*func_dict | 函数的__dict__属性 |
*func_weakreflist | 弱引用 |
*func_module | 函数的__module__ |
*func_annotations | 函数的注释 |
*func_qualname | 函数的qualified name |
- 对于一段代码,其对应的PyCodeObject对象只有一个,而对应的PyFunctionObject对象却可能有很多个,每次调用函数都会创建PyFunctionObject对象。
- 而所有PyFunctionObject对象都会关联到PyCodeObject对象。
二、无参函数的调用
1.1 函数对象的创建
- 无参函数调用是最简单的函数调用形式,创建一个简单的案例:
demo.py
def f():
...
f()
- 对应的指令字节码如下:
1 0 LOAD_CONST 0 (<code object f at 0x000001F28B9D8A50, file "demo.py", line 1>)
2 LOAD_CONST 1 ('f')
4 MAKE_FUNCTION 0
6 STORE_NAME 0 (f)
3 8 LOAD_NAME 0 (f)
10 CALL_FUNCTION 0
12 POP_TOP
14 LOAD_CONST 2 (None)
16 RETURN_VALUE
consts
code
2 0 LOAD_CONST 0 (None)
2 RETURN_VALUE
- 从上面的代码可以看出代码在经过编译后,产生了两个PyCodeObject对象,常规部分对应demo.py,code部分对应f()。
- 按照顺序从上往下看,分别是:
- 声明函数
1 0 LOAD_CONST 0 (<code object f at 0x000001F28B9D8A50, file "demo.py", line 1>) 2 LOAD_CONST 1 ('f') 4 MAKE_FUNCTION 0 6 STORE_NAME 0 (f)
- 调用函数
3 8 LOAD_NAME 0 (f) 10 CALL_FUNCTION 0 12 POP_TOP 14 LOAD_CONST 2 (None) 16 RETURN_VALUE
- 实现函数
2 0 LOAD_CONST 0 (None) 2 RETURN_VALUE
- 从上面的顺序可以看出,声明函数和实现函数被调用函数分割成两个不同的PyCodeObject,而在Python代码中,他们应该是一个完整的整体。
- 这是因为在Python中函数也是对象,在调用函数之前必须先创建这个函数对象,而这里的创建工作是通过def f()这条代码完成的,他对应了字节码指令Make_FUNCTION:
ceval.c
TARGET(MAKE_FUNCTION) {
PyObject *qualname = POP();
PyObject *codeobj = POP();
PyFunctionObject *func = (PyFunctionObject *)
PyFunction_NewWithQualName(codeobj, f->f_globals, qualname);
Py_DECREF(codeobj);
Py_DECREF(qualname);
if (func == NULL) {
goto error;
}
if (oparg & 0x08) {
assert(PyTuple_CheckExact(TOP()));
func ->func_closure = POP();
}
if (oparg & 0x04) {
assert(PyDict_CheckExact(TOP()));
func->func_annotations = POP();
}
if (oparg & 0x02) {
assert(PyDict_CheckExact(TOP()));
func->func_kwdefaults = POP();
}
if (oparg & 0x01) {
assert(PyTuple_CheckExact(TOP()));
func->func_defaults = POP();
}
PUSH((PyObject *)func);
DISPATCH();
}
- 在这里,虚拟机将f对应的qualname和PyCodeObject对象弹出,与当前PyFrameObject对象维护的global名字空间f_globals对象为参数,通过PyFunction_NewWithQualName创建了一个新的PyFunctionObject,而这个f_globals将成为函数f在运行时的global名字空间。
Objects\funcobject.c
PyObject *
PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname)
{
PyFunctionObject *op;
PyObject *doc, *consts, *module;
static PyObject *__name__ = NULL;
if (__name__ == NULL) {
__name__ = PyUnicode_InternFromString("__name__");
if (__name__ == NULL)
return NULL;
}
op = PyObject_GC_New(PyFunctionObject, &PyFunction_Type);
if (op == NULL)
return NULL;
op->func_weakreflist = NULL;
Py_INCREF(code);
op->func_code = code;
Py_INCREF(globals);
op->func_globals = globals;
op->func_name = ((PyCodeObject *)code)->co_name;
Py_INCREF(op->func_name);
op->func_defaults = NULL; /* No default arguments */
op->func_kwdefaults = NULL; /* No keyword only defaults */
op->func_closure = NULL;
consts = ((PyCodeObject *)code)->co_consts;
if (PyTuple_Size(consts) >= 1) {
doc = PyTuple_GetItem(consts, 0);
if (!PyUnicode_Check(doc))
doc = Py_None;
}
else
doc = Py_None;
Py_INCREF(doc);
op->func_doc = doc;
op->func_dict = NULL;
op->func_module = NULL;
op->func_annotations = NULL;
/* __module__: If module name is in globals, use it.
Otherwise, use None. */
module = PyDict_GetItem(globals, __name__);
if (module) {
Py_INCREF(module);
op->func_module = module;
}
if (qualname)
op->func_qualname = qualname;
else
op->func_qualname = op->func_name;
Py_INCREF(op->func_qualname);
_PyObject_GC_TRACK(op);
return (PyObject *)op;
}
- 在创建PyFunctionObject对象后,MAKE_FUNCTION还会进行一些处理函数参数的动作,并压入运行时栈中,这里由于是无参函数暂时跳过。
1.2 函数调用
- 函数调用从CALL_FUNCTION指令开始:
ceval.c
PREDICTED(CALL_FUNCTION);
TARGET(CALL_FUNCTION) {
PyObject **sp, *res;
sp = stack_pointer;
res = call_function(&sp, oparg, NULL);
stack_pointer = sp;
PUSH(res);
if (res == NULL) {
goto error;
}
DISPATCH();
}
- 虚拟机在获得了当前运行时栈栈顶指针后,直接调用了call_function:
ceval.c
Py_LOCAL_INLINE(PyObject *) _Py_HOT_FUNCTION
call_function(PyObject ***pp_stack, Py_ssize_t oparg, PyObject *kwnames)
{
PyObject **pfunc = (*pp_stack) - oparg - 1;
PyObject *func = *pfunc;
PyObject *x, *w;
Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
Py_ssize_t nargs = oparg - nkwargs;
PyObject **stack = (*pp_stack) - nargs - nkwargs;
/* Always dispatch PyCFunction first, because these are
presumed to be the most frequent callable object.
*/
if (PyCFunction_Check(func)) {
PyThreadState *tstate = PyThreadState_GET();
C_TRACE(x, _PyCFunction_FastCallKeywords(func, stack, nargs, kwnames));
}
else if (Py_TYPE(func) == &PyMethodDescr_Type) {
PyThreadState *tstate = PyThreadState_GET();
if (nargs > 0 && tstate->use_tracing) {
/* We need to create a temporary bound method as argument
for profiling.
If nargs == 0, then this cannot work because we have no
"self". In any case, the call itself would raise
TypeError (foo needs an argument), so we just skip
profiling. */
PyObject *self = stack[0];
func = Py_TYPE(func)->tp_descr_get(func, self, (PyObject*)Py_TYPE(self));
if (func != NULL) {
C_TRACE(x, _PyCFunction_FastCallKeywords(func,
stack+1, nargs-1,
kwnames));
Py_DECREF(func);
}
else {
x = NULL;
}
}
else {
x = _PyMethodDescr_FastCallKeywords(func, stack, nargs, kwnames);
}
}
else {
if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {
/* Optimize access to bound methods. Reuse the Python stack
to pass 'self' as the first argument, replace 'func'
with 'self'. It avoids the creation of a new temporary tuple
for arguments (to replace func with self) when the method uses
FASTCALL. */
PyObject *self = PyMethod_GET_SELF(func);
Py_INCREF(self);
func = PyMethod_GET_FUNCTION(func);
Py_INCREF(func);
Py_SETREF(*pfunc, self);
nargs++;
stack--;
}
else {
Py_INCREF(func);
}
if (PyFunction_Check(func)) {
x = _PyFunction_FastCallKeywords(func, stack, nargs, kwnames);
}
else {
x = _PyObject_FastCallKeywords(func, stack, nargs, kwnames);
}
Py_DECREF(func);
}
assert((x != NULL) ^ (PyErr_Occurred() != NULL));
/* Clear the stack of the function object. */
while ((*pp_stack) > pfunc) {
w = EXT_POP(*pp_stack);
Py_DECREF(w);
}
return x;
}
- 在上面代码中,首先会通过PyCFunction_Check判断是不是C函数,如果是则进入快车道。
- 如果是普通函数,则通过PyFunction_Check检查后,会进入分支_PyFunction_FastCallKeywords或_PyObject_FastCallKeywords。
- 但不管进入哪个分支,最终虚拟机会调用PyEval_EvalFrameEx。
ceval.c
PyObject *
PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
{
PyThreadState *tstate = PyThreadState_GET();
return tstate->interp->eval_frame(f, throwflag);
}
- 从PyEval_EvalFrameEx开始,虚拟机进入了函数调用状态:创建新的帧栈->在新的帧栈中执行代码。
- 在最终通过PyEval_EvalFrameEx时,PyFunctionObject对象的影响已经消失,真正对新帧栈产生影响的是在PyFunctionObject中存储的PyCodeObject对象和global名字空间。
1.3 函数执行时的名字空间
- 在执行demo.py时的字节码指令和执行f的字节码指令时的名字空间是同一个名字空间。
- 这表示在函数f中可以直接使用f函数外的符号。
- 此外在字节码指令STORENAME处,会将符号f的放入f_local中,也就是同时将f放进f_globals中,得以在函数f中实现递归。
ceval.c
case STORE_NAME:
{
PyObject *names = f->f_code->co_names;
PyObject *name = GETITEM(names, oparg);
PyObject *locals = f->f_locals;
if (locals && PyDict_CheckExact(locals) &&
PyDict_GetItem(locals, name) == v) {
if (PyDict_DelItem(locals, name) != 0) {
PyErr_Clear();
}
}
break;
}
网友评论