【CPython3.6源码分析】Python 函数机制

参考

前言

在前面我们已经知道 Python 通过PyInterpreterState 对象模拟进程的状态信息,通过 PyThreadState 对象模拟线程的状态信息,通过PyFrameObject 模拟函数调用时的栈帧信息。FrameObject 通过 f_back 指针,形成函数调用的链式结构。那么要实现函数间的调用,就必然存在多个 Frame。

PyFunctionObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// funcobject.h.21
typedef struct {
PyObject_HEAD
PyObject *func_code; /* A code object, the __code__ attribute */
PyObject *func_globals; /* A dictionary (命名空间) */
PyObject *func_defaults; /* NULL or a tuple,默认参数*/
PyObject *func_kwdefaults; /* NULL or a dict,默认参数 */
PyObject *func_closure; /* NULL or a tuple,用于闭包*/
PyObject *func_doc; /* __doc__ attribute, can be anything */
PyObject *func_name; /* __name__ attribute, a string object */
PyObject *func_dict; /* __dict__ attribute, a dict or NULL */
PyObject *func_weakreflist; /* List of weak references */
PyObject *func_module; /* __module__ attribute, can be anything */
PyObject *func_annotations; /* Annotations, a dict or NULL */
PyObject *func_qualname; /* The qualified name */
} PyFunctionObject;

如上,PyFunctionObject 中存放着对应的 CodeObject,除此之外还包含着运行需要的其他信息。这些其他信息,例如命名空间,只有在运行过程中才能获取到。可见 FuncObject 必然是在运行过程中创建的,而 CodeObject 却是在编译时创建的。

函数创建

1
2
3
4
5
6
7
8
9
10
11
12
def f():
pass

co_names: ('f',)
co_consts: (<code object f , file "demo.py", line 1>, 'f', None)

1 0 LOAD_CONST 0 (<code object f , file "demo.py", line 1>)
2 LOAD_CONST 1 ('f')
4 MAKE_FUNCTION 0 /* 稍稍留意,此处为0 */
6 STORE_NAME 0 (f)
8 LOAD_CONST 2 (None)
10 RETURN_VALUE

在前面我们已经知道,CodeObject 是一种嵌套结构,正如上面的 。命名空间 builtin 中的 ‘function’ 对应着 PyFunction_Type。既然已经知道 FuncObject 是动态创建的,那么MAKE_FUNCTION就很值得怀疑。

MAKE_FUNCTION 无参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
opcode:: MAKE_FUNCTION (argc)

Pushes a new function object on the stack. From bottom to top, the consumed
stack must consist of values if the argument carries a specified flag value
*/
TARGET(MAKE_FUNCTION) {
PyObject *qualname = POP();
PyObject *codeobj = POP();
PyFunctionObject *func = (PyFunctionObject *)
PyFunction_NewWithQualName(codeobj, f->f_globals, qualname);

... // 根据 oparg, 处理默认值:func_defaults,func_closure等
PUSH((PyObject *)func);
DISPATCH();
}

果然,从栈中取出名字+CodeObject,然后创建一个新的 FuncObject,封装属性,压入栈中。

PyFunction_NewWithQualName

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// funcobject.c.9
PyObject *
PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname)
{
PyFunctionObject *op;
PyObject *doc, *consts, *module;

op = PyObject_GC_New(PyFunctionObject, &PyFunction_Type);
... // 封装属性
op->func_code = code;
op->func_globals = globals;
_PyObject_GC_TRACK(op);
return (PyObject *)op;
}

如上,新的 FuncObject 属性封装分别在两个地方进行。完事后下一条指令STORE_NAME将栈顶的FuncObject 移入命名空间 local 中。

函数执行

CALL_FUNCTION

1
2
3
4
5
6
7
8
9
10
11
// print('1')

co_consts: ('1', None)
co_names: ('print',)

1 0 LOAD_NAME 0 (print)
2 LOAD_CONST 0 ('1')
4 CALL_FUNCTION 1
6 POP_TOP
8 LOAD_CONST 1 (None)
10 RETURN_VALUE

执行函数时,从 local 中加载对象,执行指令CALL_FUNCTION

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
opcode:: CALL_FUNCTION (argc)

Calls a function. *argc* indicates the number of positional arguments.
The positional arguments are on the stack, with the right-most argument
on top. Below the arguments, the function object to call is on the stack.
Pops all function arguments, and the function itself off the stack, and
pushes the return value.

versionchanged:: 3.6
This opcode is used only for calls with positional arguments.
*/

TARGET(CALL_FUNCTION) {
PyObject **sp, *res;
PCALL(PCALL_ALL);
sp = stack_pointer; // 获取到当前 f->f_stacktop
res = call_function(&sp, oparg, NULL); // 调用函数,同时传入函数参数个数
stack_pointer = sp; // 设置 栈顶
PUSH(res);
if (res == NULL) {
goto error;
}
DISPATCH();
}

在执行CALL_FUNCTION指令时,获得当前运行时栈栈顶指针,进入call_function

call_function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// ceval.c.4806
static PyObject *
call_function(PyObject ***pp_stack, Py_ssize_t oparg, PyObject *kwnames)
{
// 根据栈顶和函数参数个数,计算获取 func
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;

if (PyCFunction_Check(func)) {
PyThreadState *tstate = PyThreadState_GET();
stack = (*pp_stack) - nargs - nkwargs;
x = _PyCFunction_FastCallKeywords(func, stack, nargs, kwnames);
}
else {
// (func)->ob_type == &PyMethod_Type
if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {
// ((PyMethodObject *)func) -> im_self
PyObject *self = PyMethod_GET_SELF(func);
PCALL(PCALL_METHOD);
PCALL(PCALL_BOUND_METHOD);
Py_INCREF(self);
// ((PyMethodObject *)func) -> im_func
func = PyMethod_GET_FUNCTION(func);
Py_INCREF(func);
Py_SETREF(*pfunc, self); // self == &func
nargs++;
}
else {
Py_INCREF(func);
}

stack = (*pp_stack) - nargs - nkwargs;

if (PyFunction_Check(func)) {
x = fast_function(func, stack, nargs, kwnames);
}
else {
x = _PyObject_FastCallKeywords(func, stack, nargs, kwnames);
}

Py_DECREF(func);
}
...
return x;
}

如上,代码不长,先是Check_type(),若对应PyMethod_Type,还得调用PyMethod_GET_FUNCTION,获取到最终的 func,并且把self 作为位置参数传递进去。最后,判断Func_Type,进行分发。

在前面PyObject_GC_New(PyFunctionObject, &PyFunction_Type)中,传入的是PyFunction_Type,将进入到fast_function()

fast_function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// ceval.c.4916
static PyObject *
fast_function(PyObject *func, PyObject **stack,
Py_ssize_t nargs, PyObject *kwnames)
{
PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func);
PyObject *globals = PyFunction_GET_GLOBALS(func); // func -> func_globals
PyObject *argdefs = PyFunction_GET_DEFAULTS(func); // func -> func_defaults
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));
assert(nargs >= 0);
assert(kwnames == NULL || PyTuple_CheckExact(kwnames));
assert((nargs == 0 && nkwargs == 0) || stack != NULL);

PCALL(PCALL_FUNCTION);
PCALL(PCALL_FAST_FUNCTION);

/* 处理无需 关键字参数的函数 */
if (co->co_kwonlyargcount == 0 && nkwargs == 0 &&
co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE))
{
/* 无默认参数,且参数个数刚好满足 func 需要 */
if (argdefs == NULL && co->co_argcount == nargs) {
return _PyFunction_FastCall(co, stack, nargs, globals);
}
/* 无参数传递,但 func 每个参数都有默认参数 */
else if (nargs == 0 && argdefs != NULL
&& co->co_argcount == Py_SIZE(argdefs)) {
stack = &PyTuple_GET_ITEM(argdefs, 0);
return _PyFunction_FastCall(co, stack, Py_SIZE(argdefs), globals);
}
}

/* 含有关键字参数 见:_PyEval_EvalCodeWithName */
...
}

typedef struct {
PyObject_HEAD
int co_argcount; /* 位置参数 *args 个数 */
int co_kwonlyargcount; /* #keyword only arguments */
int co_nlocals; /* 局部变量个数,包含位置参数 */
PyObject *co_code; /* 字节码序列,PyStringObject 形式 */
PyObject *co_consts; /* list (所有常量) */
PyObject *co_names; /* list of strings (所有符号) */
PyObject *co_varnames; /* tuple of strings (局部变量名) */
PyObject *co_freevars; /* tuple of strings (闭包所需变量名) */
PyObject *co_cellvars; /* tuple of strings
...
} PyCodeObject;

如上,CodeObject中含有 block 所需的关键字参数,当不含关键字参数时,调用_PyFunction_FastCall。否则,执行_PyEval_EvalCodeWithName

无参数:PyFunction_FastCall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ceval.c.4879
static PyObject*
_PyFunction_FastCall(PyCodeObject *co, PyObject **args, Py_ssize_t nargs,
PyObject *globals)
{
PyFrameObject *f;
PyThreadState *tstate = PyThreadState_GET();
PyObject **fastlocals;
Py_ssize_t i;
PyObject *result;

PCALL(PCALL_FASTER_FUNCTION);
f = PyFrame_New(tstate, co, globals, NULL);
fastlocals = f->f_localsplus;

for (i = 0; i < nargs; i++) {
Py_INCREF(*args);
fastlocals[i] = *args++;
}
result = PyEval_EvalFrameEx(f,0);

++tstate->recursion_depth;
Py_DECREF(f);
--tstate->recursion_depth;

return result;
}

如上,创建一个FrameObject,执行CodeObject中的字节码。函数执行完成后,将结果返回到 result中,完成了类似C语言的函数调用过程。关于 PyEval_EvalFrameEx 的执行逻辑,在 PyCodeObject/PyFrameObject 中已经叙述过。

这里很明显能够观察到,在新的栈帧对象中,执行的是传入的CodeObject中的字节码序列,上下文环境是通过命名空间globals实现。那么函数要调用其他的函数,该怎么实现?很容易想到在fast_function中的含有关键字参数的执行逻辑。

有参数:PyEval_EvalCodeWithName

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
kwdefs = PyFunction_GET_KW_DEFAULTS(func); // func -> func_kwdefaults
closure = PyFunction_GET_CLOSURE(func); // func -> func_closure
name = ((PyFunctionObject *)func) -> func_name;
qualname = ((PyFunctionObject *)func) -> func_qualname;

if (argdefs != NULL) {
/* 有参数传递时,需判断个数,以便获取默认参数 */
d = &PyTuple_GET_ITEM(argdefs, 0);
nd = Py_SIZE(argdefs);
}
else {
d = NULL;
nd = 0;
}
return _PyEval_EvalCodeWithName((PyObject*)co, globals, (PyObject *)NULL,
stack, nargs,
nkwargs ? &PyTuple_GET_ITEM(kwnames, 0) : NULL,
stack + nargs,
nkwargs, 1,
d, (int)nd, kwdefs,
closure, name, qualname);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ceval.c.3888
_PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
PyObject **args, Py_ssize_t argcount,
PyObject **kwnames, PyObject **kwargs,
Py_ssize_t kwcount, int kwstep,
PyObject **defs, Py_ssize_t defcount,
PyObject *kwdefs, PyObject *closure,
PyObject *name, PyObject *qualname)
{
...
/* Create the frame */
f = PyFrame_New(tstate, co, globals, locals);

/* Create a dictionary for keyword parameters (**kwags) */
/* Copy positional arguments into local variables */
/* Pack other positional arguments into the *args argument */
/* Handle keyword arguments passed as two strided arrays */
/* Check the number of positional arguments */
/* Add missing positional arguments (copy default values from defs) */
/* Add missing keyword arguments (copy default values from kwdefs) */
/* Allocate and initialize storage for cell vars, and copy free
vars into frame. */
/* Copy closure variables to free variables */
/* Handle generator/coroutine/asynchronous generator */

retval = PyEval_EvalFrameEx(f,0);
}

如上,不管有参数无参数,最终都是创建栈帧对象,在新的上下文环境执行被调函数字节码指令。唯一的区别仅在于,对被调函数可能存在的参数进行处理,其中涉及到可变参数,默认参数,闭包参数等。详细内容可以阅读源码。

这里要提一句,判断函数是否含有可变参数,是通过 CodeObject->co_flags 字段实现。

1
2
3
4
5
#define CO_VARARGS	0x0004            /* CodeObject 存在可变位置参数  *args */
#define CO_VARKEYWORDS 0x0008 /* CodeObject 存在可变关键字参数 **kwargs */

/* Create a dictionary for keyword parameters (**kwags) */
if (co->co_flags & CO_VARKEYWORDS) { ... }

函数参数

如果是 C,会有值传递和地址传递这个说法。

在Python中,调用时传递的都是 对象

  • 有的对象 1 ‘abc’ (1,3) 是不可变的
  • 有的对象[1,3] {1,3} 是可变的

如果被调用函数需要修改对象内容:
1.不可变的,自然只能新创建一个对象。
2.是可变的,自然直接在对象内容删更改

而调用函数原持有的对象引用,还是指向之前的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def fun(a, b=3):
pass

fun(1)
3 10 LOAD_NAME 0 (fun)
12 LOAD_CONST 3 (1)
14 CALL_FUNCTION 1

fun(1, 2)
3 10 LOAD_NAME 0 (fun)
12 LOAD_CONST 3 (1)
14 LOAD_CONST 4 (2)
16 CALL_FUNCTION 2

fun(1, b=2)
3 10 LOAD_NAME 0 (fun)
12 LOAD_CONST 3 (1)
14 LOAD_CONST 4 (2)
16 LOAD_CONST 5 (('b',))
18 CALL_FUNCTION_KW 2

由上可见,函数的参数是位置参数还是关键字参数,是跟参数的传递方式有关,而与定义无关。函数的参数通过指令LOAD_*压入栈中,那么问题来了,上面的例子中b=3是如何生效的?

MAKE_FUNCTION 带参数

1
2
3
4
5
6
7
def fun(a, b=3):
pass

1 0 LOAD_CONST 6 ((3,))
2 LOAD_CONST 1 (<code object fun, file "demo.py", line 1>)
4 LOAD_CONST 2 ('fun')
6 MAKE_FUNCTION 1 /* 注意此处 */

此处,指令序列MAKE_FUNCTION比我们在函数创建中用到的,多了一个参数 1。所以,是时候来看下在MAKE_FUNCTION中,我们注释的那一段内容了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
-> 栈底方向
* ``0x01`` a tuple of default argument objects in positional order
* ``0x02`` a dictionary of keyword-only parameters' default values
* ``0x04`` an annotation dictionary
* ``0x08`` a tuple containing cells for free variables, making a closure
-> 栈顶方向
*/
TARGET(MAKE_FUNCTION) {
...
// 根据 oparg, 处理默认值:func_defaults,func_closure等
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(); /* keyword-only 参数默认值 */
}
if (oparg & 0x01) {
assert(PyTuple_CheckExact(TOP()));
func->func_defaults = POP(); /* 位置参数默认值 */
}
...
}

真相很明显了,默认参数、函数注解、闭包属性,都各自从栈顶POP,放入 FuncObj 属性中。下面就来看一个实际的例子。

MAKE_FUNCTION 13

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// demo.py
def f():
clu = 'clu'
def c(a:int = 1):
print(clu)

co = compile(open('demo.py').read(),'demo.py','exec'); import dis; f = co.co_consts[0]; dis.dis(f)

2 0 LOAD_CONST 1 ('clu')
2 STORE_DEREF 0 (clu)
3 4 LOAD_CONST 6 ((1,))
6 LOAD_GLOBAL 0 (int)
8 LOAD_CONST 3 (('a',))
10 BUILD_CONST_KEY_MAP 1
12 LOAD_CLOSURE 0 (clu)
14 BUILD_TUPLE 1
16 LOAD_CONST 4 (<code object c , file "demo.py", line 3>)
18 LOAD_CONST 5 ('f.<locals>.c')
20 MAKE_FUNCTION 13 /* 注意此处,13 = 8+4+1*/
22 STORE_FAST 0 (c)

如上,在def c中,同时用到了3种。那么问题来了剩下的func_kwdefaults如何实现?留给大家思考。哈~欢迎留言~