Python虚拟机函数机制之闭包和装饰器(七)
2021-07-14 13:07
标签:运算 one defaults 完整 比较 python2 lca 虚拟机 src 函数中局部变量的访问 在完成了对函数参数的剖析后,我们再来看看,在Python中,函数的局部变量时如何实现的。前面提到过,函数参数也是一种局部变量。所以,其实局部变量的实现机制与函数参数的实现机制是完全一样的。这个“一样”是什么意思呢? 之前我们剖析过Python虚拟机的一些指令,如果要访问一个变量,应该使用LOAD_NAME指令,应该依照local、global、builtin这三个名字空间里去检索变量名所对应的变量值。然后在调用函数时,Python虚拟机通过PyFrame_New创建新的PyFrameObject对象时,那个至关重要的local对象并没有创建 在对Python虚拟机机制分析的过程中,我们得知当直接调用一个脚本时,脚本对应的PyCodeObject对象中f_locals和f_globals实际上是同一个对象。那么在函数执行时,对变量的读写决不能反应在f_globals上,否则,一个函数执行完,函数外部不就可以知道函数的局部变量吗?所以,函数是如何对局部变量进行读写呢?我们来对一个带有局部变量的函数用dis模块进行字节码指令的解释 这里,我们并未发现有LOAD_NAME或者STORE_NAME这样对名字空间的读写指令,取而代之的LOAD_FAST和STORE_FAST,这是不是印证我们之前所说的,局部变量的实现机制与函数参数的实现机制是完全一样的,局部变量的读写也是在f_localsplus上 为什么函数的实现中没有使用local名字空间呢?这是因为函数中的局部变量总是固定不变的,所以在编译时就能确定局部变量使用的内存空间的位置,也能确定访问局部变量的字节码指令应该如何访问内存。有了这些信息,Python就能使用静态的方法来实现局部变量,而不需要借助于动态地查找PyDictObject对象的技术。毕竟,函数调用实在太普遍了,静态的方法可以极大地提高函数的执行效率 嵌套函数 在Python中,有一个核心的概念叫名字空间,一段代码的执行的结果不光取决于代码中的符号,更多地是取决于代码中符号的语义,而这个运行时语义正是由名字空间所决定的。名字空间是在运行时由Python虚拟机动态维护的,但有时,我们希望名字空间静态化。换句话说,我们希望代码不受名字空间变化的影响,始终保持一致的行为和结果。这样做有什么意义呢? 假如我们想要定一个基准值,然后将许多值与这个值进行比较,最简单的方法就是写一个函数: 我们以10作为基准值,与5和20进行比较,但是会发现,每次调用函数时,都必须多传一个10。于是,Python提供了嵌套函数 如上述代码,我们只设置了一次基准值。此后,在每次进行比较操作时,尽管调用的实际函数real_compare的local名字空间并没有base,而get_compare函数之外的global名字空间中有"base = 1",但是函数调用的结果显示,real_compare以我们传入的10作为base,而不是以get_compare函数之外的base作为基准书 也就是说,在real_compare这个函数作为返回值被传递给compare_with_10的时候,有一个名字空间已经与real_compare紧紧地绑定在一起,在执行real_compare的代码时,这个名字空间又恢复了,这就是将名字空间静态化的方法。这个名字空间与函数捆绑后的结果被称为一个闭包。在前面我们看到,PyFunctionObject是Python虚拟机专门为包裹字节码指令、global名字空间、默认参数值准备的大包袱,都能在PyFunctionObject中找到其位置,同样,Python中的闭包也是通过PyFunctionObject对象来实现 实现闭包的基石 我们先来看看PyCodeObject、PyFunctionObject、PyFrameObject这些我们熟悉的对象中,与闭包有关的属性。闭包的创建通常是利用嵌套函数来完成,在PyCodeObject中,与嵌套函数相关的属性是co_freevars、co_cellvars。两者具体含义如下: 考虑下面的代码: 很显然,上述的代码会编译出3个PyCodeObject对象,其中有两个,一个与函数get_func对应,一个与函数inner_func对应。那么,与get_func对应的PyCodeObject对象中co_cellvars就应该包含字符串"value",因为其嵌套作用域(inner_func的作用域)中使用了这个符号。同理,与函数inner_func对应的PyCodeObject对象中的co_freevars中应该也有字符串"value"。下面,我们来证实一下: demo4.py对应的PyCodeObject中,co_consts这个元组第一个对象就是get_func对应的PyCodeObject,我们将其取出,然后看一下其中的co_cellvars 从前面的demo4.py可以知道,尽管get_func中除去变量value,还有一个变量a,但是a没有函数内部的嵌套函数使用,所以co_cellvars只有符号value,没有符号a。我们都知道,PyCodeObject是可以嵌套PyCodeObject的,既然get_func的PyCodeObject对象被我们取出,不妨,我们再取出inner_func对应的PyCodeObject,但在这之前,我们要先看一下,inner_func的PyCodeObject,到底处于get_func对应的PyCodeObject的co_consts哪个位置 可以看到,inner_func对应的PyCodeObject中,co_freevars果然有符号value 以上,便是PyCodeObject中与闭包相关的属性。下面,我们再来看看PyFrameObject对象中,与闭包属性相关的对象。其实在这里,只有一个对象和闭包相关,就是我们的老朋友f_localsplus 在PyFrame_New函数中,有这样一段代码: extras正是f_localsplus指向的那片内存的大小,这片内存是属于运行时栈、局部变量、cell对象(co_cellvars)、和free对象(co_freevars)。下面,展现一下f_localsplus的布局: 图1-1 f_localsplus的完整内存布局 闭包的实现 在介绍完闭包的基石后,我们就可以开始追踪闭包的具体实现过程了。但是我们好像还忘了一件事,之前说过与闭包有关的对象,除了PyCodeObject、PyFrameObject,还有一个PyFunctionObject。没关系,我们很快就会了解到PyFunctionObject与闭包那些不能不说的事。不过首先,我们还是要来看一下demo5.py编译后的字节码指令: 其实,demo5.py相比demo4.py,仅仅是把get_func函数中的变量a给移除 有了demo5.py,我们就可以逐层分析PyCodeObject对象,闭包指令是长什么样的 首先,我们先得到demo5.py对应的PyCodeObject对象 demo5.py的指令序列我们已经很熟悉了,重点不是在这,而是在get_func和inner_func的指令序列 get_func指令序列 inner_func指令序列 在fast_function中,我们先前有介绍过一个快速通道,但这个快速通道不是任何函数都能进的,在进之前要满足若干条件,其中一个条件就是co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE),即co_flags为67。可惜get_func和inner_func的co_flags都不是67,注定只能乖乖走PyEval_EvalCodeEx 如果当前PyCodeObject的co_cellvars的长度不为0,将进入下面代码的分支,Python虚拟机会如同处理默认参数一样,将co_cellvars中的东西拷贝到新创建的PyFrameObject的f_localsplus中 ceval.c 在上述代码的[1]处,Python虚拟机获得了被内层嵌套函数引用的符号名,在我们的例子中,就是获得了一个字符串"value"。这里的found是被内层嵌套函数引用的符号是否已经与某个值绑定的标识,或者说与某个对象建立了约束关系。只有在内层嵌套函数引用的是外层函数的一个有默认值的参数时,这个标识才可能为1。对于我们的例子,found一定为0。因为get_func所对应的PyCodeObject中,co_varnames寻找不到符号"value"。所以Python虚拟机接下来会创建cell对象——PyCellObject cellobject.c 这个对象非常简单,仅仅维护一个ob_ref,指向一个PyObject对象,我们来看看PyCellObject的创建代码 cellobject.c 在我们的例子中,创建的PyCellObject对象维护的ob_ref指向了NULL,也就是说,现在还不知道符号value到底是什么东西,那么什么时候才能知道呢?在value = "inner"这个赋值语句执行的时候。随后,这个cell对象被拷贝到新创建的PyFrameObject对象的f_localsplus中。值的注意的是,这个对象被拷贝到的位置是co_co_nlocals + i。说明在n_localsplus中,cell对象的位置是在局部变量之后的,这完全符合图1-1所示的内存布局 在处理co_cellvars时,有一个奇怪的地方,在我们创建PyCellObject对象的过程中,代码[1]处的cellname完全忽略了。实际上,这和前面分析到的Python函数机制对局部变量符号的访问方式从对dict的查找变为对list的索引是一个道理。在get_func函数执行的过程中,对value这个cell变量的访问将通过基于索引访问f_localsplus完成,因为完全不需要再知道cellname了。这个cellname实际上是在处理内层嵌套函数引用外层函数的默认参数时产生的 在处理了cell对象之后,Python虚拟机将进入PyEval_EvalFrameEx,从而正式开始对函数get_func的调用过程,这里,我们再贴一下get_func的字节码指令序列 首先执行"0 LOAD_CONST 1"指令将PyStringObject对象"inner"压入到运行时栈,然后Python虚拟机开始执行一条对我们是全新的字节码指令——STORE_DEREF ceval.c ceval.c 从运行时栈弹出的是PyStringObject对象"inner",而从f_localsplus中取得的是PyCellObject对象。原来,STORE_DEREF是要设置PyCellObject对象中的ob_ref cellobject.c 这样一来,f_localsplus就发生了变化,如图1-2 图1-2 设置cell对象之后的get_func函数的PyFrameObject对象 现在在get_func的环境中我们知道了value符号对应着一个PyStringObject对象,但是闭包的作用是将这个约束进行冻结,使得在嵌套函数inner_func被调用时还能使用这个约束。这一次,又需要用到PyFunctionObject这个对象了。在执行demo5.py中def inner_func()表达式时,Python虚拟机就将(value, "inner")这个约束塞到PyFunctionObject中 ceval.c "6 LOAD_CLOSURE 0"指令将刚刚放置好的PyCellObject对象取出,并压入运行时栈,接着执行"9 BUILD_TUPLE 1"指令将PyCellObject对象打包进一个tupple中,显然,这个tupple可以放置多个PyCellObject对象。不过,我们的例子只有一个PyCellObject对象 随后,Python虚拟机通过执行"12 LOAD_CONST 2"指令将inner_func对应的PyCodeObject对象也压入到运行时栈,接着以一个"15 MAKE_CLOSURE 0"指令完成约束与PyCodeObject的绑定 ceval.c 表达式"def inner_func()"所对应的最后一条"18 STORE_FAST 0"指令将所创建的PyFunctionObject对象放置到了f_localsplus中。这样,f_localsplus又发生了变化 图1-3 设置function对象之后的get_func函数中的PyFrameObject对象 在get_func的最后,这个新建的PyFunctionObject对象将作为返回值返回给上一个栈帧,并被压入到该栈帧的运行时栈中 使用闭包(closure) 闭包是在get_func中创建的,而对于闭包的使用,则是在inner_func中。在执行"show_value()"对应的CALL_FUNCTION时,和inner_func对应的PyCodeObject中的co_flags里包含了CO_NESTED,所以在fast_function中依旧不能通过快速通道的验证,还是要进入到PyEval_EvalCodeEx 这里,我们再看一下inner_func对应的字节码指令序列 我们已经看到,inner_func对应的PyCodeObject这种co_freevars里有引用外部作用域的符号名,在PyEval_EvalCodeEx中,就会对这个co_freevars进行处理 ceval.c 其中,closure变量作为最后一个函数参数传递进来,我们看看在fast_function中到底传进来什么 原来传进来的就是在PyFunctionObject对象中与PyCodeObject对象绑定的装满了PyCellObject对象的tupple,所以在PyEval_EvalCodeEx中,进行的动作就是将tupple中一个个PyCellObject对象放入到f_localsplus中相应的位置。处理完closure后,inner_func对应的PyFrameObject中的f_localsplus如图1-4所示 图1-4 设置cell对象之后的inner_func函数的PyFrameObject对象 所以,在inner_func调用过程中,当引用到外层作用域的符号时,一定是到f_localsplus中的free变量区域中获得符号对应的值。这正是inner_func函数中"print(value)"表达式对应的第一条字节码指令"0 LOAD_DEREF 0" ceval.c 装饰器(Decorator) 在closure技术的基础上,Python实现的装饰器(Decorator),来看下面的例子: 实际上,我们可以完全不用decorator,而实现同样的效果,只需要对demo6.py做小小的修改 会发现demo6.py和demo7.py的输出结果相同。实际上,基于上面对closure的剖析,装饰器的行为就很好理解了,装饰器只是用一个函数来包装另一个函数,类似"func = should_say(func)"的形式。现在,我们来看看demo6.py和demo7.py中部分编译结果 demo6.py字节码指令序列 demo7.py字节码指令序列 在demo7.py中,"15 STORE_NAME 1"和"21 LOAD_NAME 1"这两条字节码指令互为逆运算,可以删除。如此一来,demo7.py编译后的字节码指令序列和demo6.py编译后的字节码指令序列,除了"LOAD_NAME 0"的位置不同外,其余的都完全相同。 Python虚拟机函数机制之闭包和装饰器(七) 标签:运算 one defaults 完整 比较 python2 lca 虚拟机 src 原文地址:https://www.cnblogs.com/beiluowuzheng/p/9535533.html>>> def f(a, b):
... c = a + b
... print(c)
...
>>> import dis
>>> dis.dis(f)
2 0 LOAD_FAST 0 (a)
3 LOAD_FAST 1 (b)
6 BINARY_ADD
7 STORE_FAST 2 (c)
3 10 LOAD_FAST 2 (c)
13 PRINT_ITEM
14 PRINT_NEWLINE
15 LOAD_CONST 0 (None)
18 RETURN_VALUE
>>> def compare(base, value):
... return value > base
...
>>> compare(10, 5)
False
>>> compare(10, 20)
True
>>> base = 1
>>> def get_compare(base):
... def real_compare(value):
... return value > base
... return real_compare
...
>>> compare_with_10 = get_compare(10)
>>> compare_with_10(5)
False
>>> compare_with_10(20)
True
# cat demo4.py
def get_func():
a = 1
value = "inner"
def inner_func():
print(value)
return inner_func
>>> source = open("demo4.py").read()
>>> co = compile(source, "demo4.py", "exec")
>>> co.co_consts
(
, None)
>>> get_func_co = co.co_consts[0]
>>> get_func_co.co_name
‘get_func‘
>>> get_func_co.co_cellvars
(‘value‘,)
>>> get_func_co.co_consts
(None, 1, ‘inner‘,
)
>>> inner_func_co = get_func_co.co_consts[3]
>>> inner_func_co.co_freevars
(‘value‘,)
ncells = PyTuple_GET_SIZE(code->co_cellvars);
nfrees = PyTuple_GET_SIZE(code->co_freevars);
extras = code->co_stacksize + code->co_nlocals + ncells + nfrees;
# cat demo5.py
def get_func():
value = "inner"
def inner_func():
print(value)
return inner_func
show_value = get_func()
show_value()
>>> source = open("demo5.py").read()
>>> co = compile(source, "demo5.py", "exec")
>>> import dis
>>> dis.dis(co)
1 0 LOAD_CONST 0 (
)
3 MAKE_FUNCTION 0
6 STORE_NAME 0 (get_func)
10 9 LOAD_NAME 0 (get_func)
12 CALL_FUNCTION 0
15 STORE_NAME 1 (show_value)
11 18 LOAD_NAME 1 (show_value)
21 CALL_FUNCTION 0
24 POP_TOP
25 LOAD_CONST 1 (None)
28 RETURN_VALUE
>>> co.co_consts
(
, None)
>>> get_func_co = co.co_consts[0]
>>> get_func_co.co_flags
3
>>> dis.dis(get_func_co)
2 0 LOAD_CONST 1 (‘inner‘)
3 STORE_DEREF 0 (value)
4 6 LOAD_CLOSURE 0 (value)
9 BUILD_TUPLE 1
12 LOAD_CONST 2 (
)
15 MAKE_CLOSURE 0
18 STORE_FAST 0 (inner_func)
7 21 LOAD_FAST 0 (inner_func)
24 RETURN_VALUE
>>> get_func_co.co_consts
(None, ‘inner‘,
)
>>> inner_func_co = get_func_co.co_consts[2]
>>> inner_func_co.co_flags
19
>>> dis.dis(inner_func_co)
5 0 LOAD_DEREF 0 (value)
3 PRINT_ITEM
4 PRINT_NEWLINE
5 LOAD_CONST 0 (None)
8 RETURN_VALUE
PyObject *
PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
PyObject **args, int argcount, PyObject **kws, int kwcount,
PyObject **defs, int defcount, PyObject *closure)
{
……
if (PyTuple_GET_SIZE(co->co_cellvars))
{
int i, j, nargs, found;
char *cellname, *argname;
PyObject *c;
nargs = co->co_argcount;
if (co->co_flags & CO_VARARGS)
nargs++;
if (co->co_flags & CO_VARKEYWORDS)
nargs++;
for (i = 0; i co_cellvars); ++i)
{
//[1]:获得被嵌套函数共享的符号名
cellname = PyString_AS_STRING(
PyTuple_GET_ITEM(co->co_cellvars, i));
found = 0;
for (j = 0; j co_varnames, j));
if (strcmp(cellname, argname) == 0)
{
c = PyCell_New(GETLOCAL(j));
if (c == NULL)
goto fail;
GETLOCAL(co->co_nlocals + i) = c;
found = 1;
break;
}
}
//处理被嵌套函数共享外层函数的默认参数
if (found == 0)
{
c = PyCell_New(NULL);
if (c == NULL)
goto fail;
SETLOCAL(co->co_nlocals + i, c);
}
}
}
……
}
typedef struct {
PyObject_HEAD
PyObject *ob_ref; /* Content of the cell or NULL when empty */
} PyCellObject;
PyObject *
PyCell_New(PyObject *obj)
{
PyCellObject *op;
op = (PyCellObject *)PyObject_GC_New(PyCellObject, &PyCell_Type);
if (op == NULL)
return NULL;
op->ob_ref = obj;
Py_XINCREF(obj);
_PyObject_GC_TRACK(op);
return (PyObject *)op;
}
>>> dis.dis(get_func_co)
2 0 LOAD_CONST 1 (‘inner‘)
3 STORE_DEREF 0 (value)
4 6 LOAD_CLOSURE 0 (value)
9 BUILD_TUPLE 1
12 LOAD_CONST 2 (
)
15 MAKE_CLOSURE 0
18 STORE_FAST 0 (inner_func)
7 21 LOAD_FAST 0 (inner_func)
24 RETURN_VALUE
PyObject * PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
{
……
freevars = f->f_localsplus + co->co_nlocals;
……
}
case STORE_DEREF:
w = POP();
x = freevars[oparg];
PyCell_Set(x, w);
Py_DECREF(w);
continue;
#define PyCell_SET(op, v) (((PyCellObject *)(op))->ob_ref = v)
int PyCell_Set(PyObject *op, PyObject *obj)
{
if (!PyCell_Check(op)) {
PyErr_BadInternalCall();
return -1;
}
Py_XDECREF(((PyCellObject*)op)->ob_ref);
Py_XINCREF(obj);
PyCell_SET(op, obj);
return 0;
}
case LOAD_CLOSURE:
x = freevars[oparg];
Py_INCREF(x);
PUSH(x);
if (x != NULL)
continue;
break;
case MAKE_CLOSURE:
{
v = POP(); //获得PyCodeObject对象
x = PyFunction_New(v, f->f_globals);//绑定global名字空间
Py_DECREF(v);
if (x != NULL)
{
v = POP();//获得tupple,其中包含PyCellObject对象的集合
err = PyFunction_SetClosure(x, v);//绑定约束集合
Py_DECREF(v);
}
//处理拥有默认值的参数
if (x != NULL && oparg > 0)
{
v = PyTuple_New(oparg);
if (v == NULL)
{
Py_DECREF(x);
x = NULL;
break;
}
while (--oparg >= 0)
{
w = POP();
PyTuple_SET_ITEM(v, oparg, w);
}
err = PyFunction_SetDefaults(x, v);
Py_DECREF(v);
}
PUSH(x);
break;
}
>>> dis.dis(inner_func_co)
5 0 LOAD_DEREF 0 (value)
3 PRINT_ITEM
4 PRINT_NEWLINE
5 LOAD_CONST 0 (None)
8 RETURN_VALUE
PyObject *
PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
PyObject **args, int argcount, PyObject **kws, int kwcount,
PyObject **defs, int defcount, PyObject *closure)
{
……
if (PyTuple_GET_SIZE(co->co_freevars))
{
int i;
for (i = 0; i co_freevars); ++i)
{
PyObject *o = PyTuple_GET_ITEM(closure, i);
Py_INCREF(o);
freevars[PyTuple_GET_SIZE(co->co_cellvars) + i] = o;
}
}
……
}
//funcobject.c
#define PyFunction_GET_CLOSURE(func) (((PyFunctionObject *)func) -> func_closure)
//ceval.c
static PyObject *
fast_function(PyObject *func, PyObject ***pp_stack, int n, int na, int nk)
{
……
return PyEval_EvalCodeEx(co, globals,
(PyObject *)NULL, (*pp_stack) - n, na,
(*pp_stack) - 2 * nk, nk, d, nd,
PyFunction_GET_CLOSURE(func));
}
case LOAD_DEREF:
x = freevars[oparg];//获得PyCellObject对象
w = PyCell_Get(x);//获得PyCellObject.ob_obj指向的对象
if (w != NULL)
{
PUSH(w);
continue;
}
……
# cat demo6.py
def should_say(fn):
def say(*args):
print("say something...")
fn(*args)
return say
@should_say
def func():
print("in func")
func()
# python2.5 demo6.py
say something...
in func
# cat demo7.py
def should_say(fn):
def say(*args):
print("say something...")
fn(*args)
return say
def func():
print("in func")
func = should_say(func)
func()
# python2.5 demo7.py
say something...
in func
@should_say
def func():
//字节码指令序列
9 LOAD_NAME 0 (should_say)
12 LOAD_CONST 1 (
)
15 MAKE_FUNCTION 0
18 CALL_FUNCTION 1
21 STORE_NAME 1 (func)
print("in func")
def func():
//字节码指令序列
9 LOAD_CONST 1 (
)
12 MAKE_FUNCTION 0
15 STORE_NAME 1 (func)
print("in func")
func = should_say(func)
//字节码指令序列
18 LOAD_NAME 0 (should_say)
21 LOAD_NAME 1 (func)
24 CALL_FUNCTION 1
27 STORE_NAME 1 (func)
上一篇:Python的生成器
下一篇:SpringCloud03 Ribbon知识点、 Feign知识点、利用RestTemplate+Ribbon调用远程服务提供的资源、利用feign调用远程服务提供的资源
文章标题:Python虚拟机函数机制之闭包和装饰器(七)
文章链接:http://soscw.com/index.php/essay/105125.html