设为首页 收藏本站
查看: 1064|回复: 0

[经验分享] 《python源代码剖析》笔记 python虚拟机中的函数机制

[复制链接]

尚未签到

发表于 2015-4-25 05:58:16 | 显示全部楼层 |阅读模式
  本文为senlie原创,转载请保留此地址:http://blog.iyunv.com/zhengsenlie
  


  1.Python虚拟机在运行函数调用时会动态地创建新的 PyFrameObject对象,

这些PyFrameObject对象之间会形成PyFrameObject对象链,模拟x86平台上执行时栈





2.PyFuctionObject对象



typedef struct {
PyObject_HEAD
PyObject *func_code; //相应函数编译后的PyCodeObject对象
PyObject *func_globals; //函数执行时的global空间
PyObject *func_defaults; //默认參数(tuple或NULL)
PyObject *func_closure; //NULL or a tuple of cell objects,用于实现closure
PyObject *func_doc; //函数的文档(PyStringObject)
PyObject *func_name; //函数名称,函数的__name__属性,(PyStringObject)
PyObject *func_dict; //函数的__dict__属性(PyDictObject或NULL)
PyObject *func_weakreflist;
PyObject *func_module; //函数的__module__,能够是不论什么对象
} PyFunctionObject;

函数的声明和函数的实现是分离在不同的PyCodeObject中的

def f()创建了函数对象

DSC0000.jpg





PYCodeObject:一个Code Block的静态信息。比方a = 1,符号a和值1以及它们的联系是一种静态信息,分别存储在

PyCodeObject的常量表co_consts,符号表co_names以及字节码序列co_code中

PyFunctionObject:包括函数的静态信息,由func_code指向函数代码相应的PyCodeObject对象,还包括了函数在运行时

的动态信息,如func_globals。





名字空间里键值对是动态信息,必须在执行时动态创建





一段代码仅仅能相应一个PyCodeObject,却能够相应多个PyFunctionObject。

DSC0001.jpg



3.无參函数调用

??图11-2,图11-3 为什么用这两个 strRef,internStr

def f():
#LOAD_CONST 0
#MAKE_FUNCTION 0
#STORE_NAME 0
print "Function"
#LOAD_CONST 1
#PRINT_ITEM
#PRINT_NEWLINE
#LOAD_CONST
#RETURN_VALUE
f()
#LOAD_NAME 0
#CALL_FUNCTION 0
#POP_TOP
#LOAD_CONST 1
#RETURN_VALUE
  
  




  def f()创建了函数对象,如图11-6所看到的
DSC0002.jpg
  call_function函数调用

static PyObject *
call_function(PyObject ***pp_stack, int oparg
#ifdef WITH_TSC
, uint64* pintr0, uint64* pintr1
#endif
)
{
//[1]:处理函数參数信息
int na = oparg & 0xff; //位置參数、扩展位置參数个数
int nk = (oparg>>8) & 0xff; //键參数、扩展键參数个数
int n = na + 2 * nk; //总共的參数个数 nk*2是由于(键,值)对中键和值各占一个位置
//[2]:获得PyFunctionObject对象
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) {
} else
//[3]:对PyFunctionObject对象进行调用
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

对于一般函数(除Python独有的函数(如Draw(x, *key, **dict))外的函数),PyEval_EvalFrameEx,进入一个新的PyFrameObject(栈桢)环境中開始运行新的字节码指令序列的循环

还有一条路径,PyEval_EvalCodeEx

static PyObject *
fast_function(PyObject *func, PyObject ***pp_stack, int n, int na, int nk)
{
PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func);
PyObject *globals = PyFunction_GET_GLOBALS(func);
PyObject *argdefs = PyFunction_GET_DEFAULTS(func);
PyObject **d = NULL;
int nd = 0;

//...
//[1]:一般函数的高速通道
if (argdefs == NULL && co->co_argcount == n && nk==0 &&
co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)) {
PyFrameObject *f;
PyObject *retval = NULL;
PyThreadState *tstate = PyThreadState_GET();
PyObject **fastlocals, **stack;
int i;

f = PyFrame_New(tstate, co, globals, NULL);
//...
retval = PyEval_EvalFrameEx(f,0);
//...
return retval;
}
if (argdefs != NULL) {
d = &PyTuple_GET_ITEM(argdefs, 0);
nd = Py_SIZE(argdefs);
}
return PyEval_EvalCodeEx(co, globals,
(PyObject *)NULL, (*pp_stack)-n, na,
(*pp_stack)-2*nk, nk, d, nd,
PyFunction_GET_CLOSURE(func));
}

4.函数运行时的名字空间

LOAD_NAME会依次在f_locals, f_globals, f_builtins搜索符号

在运行func_0.py的字节码指令序列时的global名字空间和运行函数f的字节码指令序列时的global名字空间实际上是同一个名字空间

DSC0003.jpg





5.

參数类型:位置參数、键參数、扩展位置參数、扩展键參数



          def f(  a,
    n = "Python",       *list,               **key)

扩展位置參数、扩展键參数用局部变量实现

oparg:參数个数,仅仅需记录位置參数和键參数的个数,扩展位置參数和扩展键參数是特殊的位置參数和键參数

oparg有两个字节,低字节-->位置參数的个数,高字节-->键參数的个数

记录參数须要的内存数:n = na + 2*nk,由于位置參数仅仅需一条LOAD_CCONST,而键參数由于(键,值)对须要两条LOAD_CONST

一个參数是位置參数还是键參数由实參决定,非键參数必须在键參数之前,eg.

def fun(a, b):
pass
fun(1, b = 2)
DSC0004.jpg
DSC0005.jpg


DSC0006.jpg


DSC0007.jpg





5.位置參数的传递

def f(name, age):
#LOAD_CONST 0
#MAKE_FUNCTION 0
#STORE_NAME 0
age += 5
#LOAD_FAST 1
#LOAD_CONST 5
#INPLACE_ADD
#STORE_FAST 1
print "[", name, ",", age, "]"
#LOAD_CONST 2
#PRINT_ITEM
#LOAD_FAST 0
#PRINT_ITEM
#LOAD_CONST 3
#PRINT_ITEM
#LOAD_FAST 1
#PRINT_ITEM
#LOAD_CONST 4
#PRINT_ITEM
#PRINT_NEWLINE
#LOAD_CONST 0
#RETURN_VALUE
age = 5;
print age
f("Robert", age)
#LOAD_NAME 0
#LOAD_CONST 2
#LOAD_NAME 1
#CALL_FUNCTION 2
#POP_TOP
print agecall_function --> fast_function

DSC0008.jpg



位置參数的訪问

LOAD_FAST i, 将f_localsplus中的对象到执行时栈中

STORE_FAST i, 将执行时栈顶中的值存放到f_localsplus

在调用函数时,Python将函数參数值从左到右到执行时栈中,在fast_function中,又将这些參数

依次复制到新建的与函数相应的PyFrameObject对象的f_localsplus中,终于的效果就是,Python虚拟机将

函数调用时传入的參数从左到右地依次存放在新建的PyFrameObject对象的f_localsplus中。

在訪问函数參数时,Python虚拟机没有依照通常訪问符号的做法,去查什么名字空间,而是直接通过

一个索引(偏移位置)来訪问f_localsplus中存储的符号相应的值对象。

DSC0009.jpg



位置參数的默认值

def f(a = 1, b = 2):
#LOAD_CONST 0
#LOAD_CONST 1
#LOAD_CONST 2
#MAKE_FUNCTION 2
#STORE_NAME 0
print a + b
#LOAD_FAST 0
#LOAD_FAST 1
#BINARY_ADD
#PRINT_ITEM
#PRINT_NEWLINE
#LOAD_CONST 0
#RETURN_VALUE
f()
#LOAD_NAME 0
#CALL_FUNCTION 0
#POP_TOP
f(b=3)
#LOAD_NAME 0
#LOAD_CONST 3
#LOAD_CONST 4
#CALL_FUNCTION 256
#POP_TOP
#LOAD_CONST 5
#RETURN_VALUE

不管函数有无參数,其def语句编译后的结果都是一样的,区别是在进行函数调用的时候产生的,

无參函数在调用前公将PyFunctionObject对象压入执行时栈,而带參函数还需将參数也压入执行时栈

而有默认參数值的函数的def语句编译后还会多出几条LOAD_CONST字节码,将默认值压入栈中,

然后在MAKE_FUNCTION中,将这些默认值所有存放到一个PyTupleObject对象中,再将该对象设置为

PyFrameObject.func_defaults的值。这样,函数參数的默认值也成为了PyFunctionObject对象的一部分。

总结:PyFunctionObject对象包含三个基本的内容,PyCodeObject, globals名字空间和func_defaults



PyObject *
PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
PyObject **args, int argcount, PyObject **kws, int kwcount,
PyObject **defs, int defcount, PyObject *closure)
{
register PyFrameObject *f;
register PyObject *retval = NULL;
register PyObject **fastlocals, **freevars;
PyThreadState *tstate = PyThreadState_GET();
PyObject *x, *u;
//[1]:创建PyFrameObject对象
f = PyFrame_New(tstate, co, globals, locals);
fastlocals = f->f_localsplus;
freevars = f->f_localsplus + co->co_nlocals;
//[a]:遍历键參数,确定函数的def语句中是否出现了键參数的名字
for (i = 0; i < kwcount; i++) {
PyObject *keyword = kws[2*i];
PyObject *value = kws[2*i + 1];
int j;
//:在函数的变量名表中寻找keyword
//...
for (j = 0; j < co->co_argcount; j++) {
PyObject *nm = co_varnames[j];
int cmp = PyObject_RichCompareBool(
keyword, nm, Py_EQ);
if (cmp > 0)
goto kw_found;
else if (cmp < 0)
goto fail;
}
if (kwdict == NULL) {
PyObject *kwd_str = kwd_as_string(keyword);
if (kwd_str) {
PyErr_Format(PyExc_TypeError,
&quot;%.200s() got an unexpected &quot;
&quot;keyword argument '%.400s'&quot;,
PyString_AsString(co->co_name),
PyString_AsString(kwd_str));
Py_DECREF(kwd_str);
}
goto fail;
}
PyDict_SetItem(kwdict, keyword, value);
continue;
kw_found:
if (GETLOCAL(j) != NULL) {
goto fail;
}
Py_INCREF(value);
SETLOCAL(j, value);
}
if (co->co_argcount > 0 ||
co->co_flags & (CO_VARARGS | CO_VARKEYWORDS)) {
int i;
int n = argcount;
PyObject *kwdict = NULL;
//n为CALL_FUNCTION的參数指示的传入的位置參数个数,即na,这里为0
//...
//[2]:推断是否使用參数的默认值
if (argcount < co->co_argcount) {
//m 一般位置參数的个数
int m = co->co_argcount - defcount;
//[3]:函数调用者必须传递一般位置參数的參数值
for (i = argcount; i < m; i++) {
if (GETLOCAL(i) == NULL) {
goto fail;
}
}
//[4]:n > m意味着调用者希望替换一些默认位置參数的默认值
if (n > m)
i = n - m;
else
i = 0;
//[5]:设置默认位置參数的默认值
for (; i < defcount; i++) {
if (GETLOCAL(m+i) == NULL) {
PyObject *def = defs;
Py_INCREF(def);
SETLOCAL(m+i, def);
}
}
}
}
retval = PyEval_EvalFrameEx(f,0);
return retval;
}



当终于须要设置默认&#20540;的參数个数确定之后,Python虚拟机会从PyFrameObject对象的func_defaults中将这些參数取出,

并通过SETLOCAL将其放入PyFrameObject对象的f_localsplus所管理的内存块中。





在编译时,Python会将函数的def语句中出现的參数的名称都记录在变量名表(co_varnames)中。





对于第二次调用

Python虚拟机会先在PyCodeObject对象中的co_varnames中查找'b',得到它相应的序号,然后通过SETLOCAL将

新建的PyFrameObject对象中的f_localsplus中參数b相应的位置设置为3。接下来再为须要设置默认&#20540;的默认位置參数

设置默认&#20540;。





扩展位置參数和扩展键參数,是作为局部变量来实现的

*list是由PyTupleObject实现,而**key是由PyDictObject对象实现

在编译一个函数时,假设发现了*list这种扩展位置參数形式,会在PyCodeObject对象的co_flags中加入标识符号:CO_VARARGS,

假设发现**key扩展键參数的形式,会向co_flags中加入 CO_VARKEYWORDS



DSC00010.jpg



PyObject *
PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
PyObject **args, int argcount, PyObject **kws, int kwcount,
PyObject **defs, int defcount, PyObject *closure)
{
register PyFrameObject *f;
register PyObject **fastlocals, **freevars;
PyThreadState *tstate = PyThreadState_GET();
PyObject *x, *u;
//创建PyFrameObject对象
f = PyFrame_New(tstate, co, globals, locals);
fastlocals = f->localsplus;
freevars = f->f_localsplus + f->f_nlocals;
//[1]:推断是否须要处理扩展位置參数或扩展键參数
if(co->co_argcount > 0 || co->co_flags & (CO_VARARGS | CO_VARKEYWORDS)){
int i;
int n = argcount;
if(argcount > co->co_argcount ){
n = co->co_argcount;
}
//[2]:设置位置參数的參数值
for(i = 0; i < n; i++){
x = args;
SETLOCAL(i, x);
}
//[3]:处理扩展位置參数
if(co->co_flags & CO_VARARGS){
//[4]:将PyTupleObject对象放入到f_localsplus中
u = PyTuple_New(argcount - n );
SETLOCAL(co->co_argcount, u);
//[5]:将扩展位置參数放入到PyTupleObject中
for( i = n; i < argcount; i++){
x = args;
PyTuple_SET_ITEM(u, i - n, x);
}
}
}

}



Python虚拟机会在函数的PyCodeObject对象的变量名对象表(co_varnames)中查找键參数的名字,仅仅有在查找失败时,才干

确定该键參数应该属于一个扩展键參数。

PyObject *
PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
PyObject **args, int argcount, PyObject **kws, int kwcount,
PyObject **defs, int defcount, PyObject *closure)
{
//...
if(co->co_flags > 0 || co->co_flags & (CO_VARARGS | CO_VARKEYWORDS)){
int i;
int n = argcount;
PyObject *kwdict = NULL;
//...
//[1]:创建PyDictObject对象,并将其放到f_localsplus中
if(co->co_flags & CO_VARKEYWORDS){
kwdict = PyDict_New();
i = co->co_argcount;
//[2]:PyDictObject对象必须在PyTupleObject之后
if(co->co_flags & CO_VARARGS)
i++;
SETLOCAL(i, kwdict);
}
//遍历键參数,确定函数的def语句中是否出现了键參数的名字
for(i = 0; i < kwcount; i++){
PyObject *keyword = kws[2 * i];
PyObject *value = kws[2 * i + 1];
int j;
//在函数的变量名表中寻找keyword
for(j = 0; j < co->co_argcount; j++){
PyObject *nm = PyTuple_GET_ITEM(co->co_varnames, j);
int cmp = PyObject_RichCompareBool(keyword, nm, Py_EQ);
if(cmp > 0)//在co_varnames中找到keyword
break;
else if (cmp < 0)
goto fail;
}
//[3]:keyword没有在变量名对象表中出现
if(j >= co->co_argcount){
PyDict_SetItem(kwdict, keyword, value);
}
//keyword在变量名对象表中出现
else{
SETLOCAL(j, value);
}
}
}
}

DSC00011.jpg



6.函数中局部变量的訪问

局部变量也是利用LOAD_FAST和 STORE_FAST来操作的,也是存放在f_localsplus中执行时栈前面的那段内存空间中。

函数的实现中没有使用local名字空间,是由于函数中的局部变量总是固定不变的,编译时就能确定,不用动态查找。

??那local名字空间还有什么用?





7.嵌套函数、闭包和decorator

名字空间-->动态-->可将其静态化-->闭包

闭包:名字空间与函数绑定后的结果,通常利用嵌套函数来完毕。





co_cellvars: tuple,保存嵌套作用域中使用的变量名

co_freevars: tuple,保存外部作用域中的变量名

f_localsplus指向的内存有四部分:执行时栈、局部变量、cell对象、free对象

DSC00012.jpg



外部函数的co_cellvars记录着嵌套作用域中使用的变量名,在创建内部函数的时候,将

co_cellvars中的符号相应的&#20540;保存到内存函数的f_localsplus的cell对象中,从而实现闭包。

在訪问的时候





def get_func():
LOAD_CONST 0
MAKE_FUNCTION
STORE_NAME 0
value = &quot;inner&quot;
LOAD_CONST 1
STORE_DEREF 0
def inner_func():
LOAD_CLOSUER 0
BUILD_TUPLE
LOAD_CONST 2
MAKE_CLOSURE 0
STORE_FAST 0
print value
LOAD_DEREF 0
PRINT_ITEM
PRINT_NEWLINE
LOAD_CONST 0
RETURN_VALUE
return inner_func
LOAD_FAST 0
RETURN_VALUE
show_value = get_func()
LOAD_NAME 0
CALL_FUNCTION 0
STORE_NAME 1
show_value()
LOAD_NAME 1
CALL_FUNCTION 0
POP_TOP
LOAD_CONST
RETURN_VALUE

在PyEval_EvalCodeEx中,Python虚拟机会如同处理默认參数一样,将co_cellvars中的东西复制到新建的PyFrameObject的

f_localsplus中





定义时

DSC00013.jpg

DSC00014.jpg

调用时

DSC00015.jpg





Decorator --> 好像是回调函数

在python中,decorator是func = should_say(func)的一种包装方式,理解decorator的关键在于理解closure

def should_say(fn):
def say(*args):
print 'say something ...'
fn(*args)
return say
@should_say
def func():
print 'in func'

和以下的事实上是一样的。

#...
def func():
print 'in func'
func = should_say(func)
func()



  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-60312-1-1.html 上篇帖子: 谈关于Python 与开源 ,Python中的代码与实现原理 下篇帖子: 「学习笔记——Python」Python 的错误和异常处理
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表