Python虚拟机之函数机制(一)

PyFunctionObject对象

在Python中,任何一个东西都是对象,函数也不例外。函数这种抽象机制,是通过一个Python对象——PyFunctionObject来实现的

typedef struct {
    PyObject_HEAD
    PyObject *func_code;	//编译后的PyCodeObject对象
    PyObject *func_globals;	//函数运行时的global名字空间
    PyObject *func_defaults;	//默认参数(tupple或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;

  

在Python中,有两个对象和函数有关,PyCodeObject和PyFunctionObject。PyCodeObject对象是对一段Python源代码的静态表示,Python源代码经过编译后,对一个Code Block会产生一个且只有一个PyCodeObject对象,这个PyCodeObject对象包含了在这个Code Block的一些静态信息,所谓静态信息即可以在源代码中看到的信息,如Code Block有a = 1这样的表达式,那么符号a和值1,以及它们之间的联系就是一种静态信息,这些信息会分别存储在PyCodeObject的常量表co_consts、符号表co_names以及字节码co_code中,这些信息是编译时就可以得到,因为PyCodeObject是编译时的结果

而PyFunctionObject则不同,PyFunctionObject对象是Python代码在运行时动态产生的,更准确的说,是在执行一个def语句时创建的。在PyFunctionObject中,也会包含这个函数的静态信息,这些信息存储在func_code中。实际上,func_code一定会指向函数代码所对应的PyCodeObject对象。除此之外,PyFunctionObject对象还包含一些函数在执行时必须的动态信息,即上下文信息,比如func_globals,就是函数在执行时关联的global作用域。global作用于的符号和值对应的关系必须在运行时才能确定,所以这部分必须在运行时动态创建,无法存储在PyCodeObject中

对于一段Python代码,其对应的PyCodeObject对象只有一个,而代码所对应的PyFunctionObject对象却可能有多个,比如一个函数多次调用,Python会在运行时创建多个PyFunctionObject对象,每一个PyFunctionObject对象的func_code域都会关联到这个PyCodeObject对象,如图1-1展示了PyFunctionObject与PyCodeObject对象之间的关系:

图1-1

无参数调用

我们对Python函数的剖析从无参数的调用开始,因为无参数调用是最简单的形式

# cat demo.py
def f():
    print("Function")

f()

# python2.5
……
>>> source = open("demo.py").read()
>>> co = compile(source, "demo.py", "exec")
>>> import dis
>>> dis.dis(co)
  1           0 LOAD_CONST               0 (<code object f at 0x7fd9831c3648, file "demo.py", line 1>)
              3 MAKE_FUNCTION            0
              6 STORE_NAME               0 (f)

  5           9 LOAD_NAME                0 (f)
             12 CALL_FUNCTION            0
             15 POP_TOP
             16 LOAD_CONST               1 (None)
             19 RETURN_VALUE
>>> from demo import f
Function
>>> dis.dis(f)
  2           0 LOAD_CONST               1 (‘Function‘)
              3 PRINT_ITEM
              4 PRINT_NEWLINE
              5 LOAD_CONST               0 (None)
              8 RETURN_VALUE

  

demo.py会产生两个PyCodeObject对象,一个是源文件编译后所生成的PyCodeObject对象,还有一个是函数f编译后所生成的PyCodeObject对象,包含在源文件对应的PyCodeObject对象中。

从上面的例子可以看到,在dis编译demo.py时,并没有将f函数的字节码编译出来,而当我们把函数f传给dis模块时,我们才得到函数f对应的字节码。这表明,函数f的字节码根本不在demo.py所对应的PyCodeObject对象的co_code域中,而是在函数f所对应的PyCodeObject对象中,函数f所对应的PyCodeObject对象,包含在demo.py所对应的PyCodeObject对象中,这是我们必须注意的字节码指令的层次结构。因此,函数的声明与函数的实现实际上是分离的,它们分离在不同的PyCodeObject对象中。尽管从源代码上来看,我们的第一直觉认为声明和实现应该是在一起的,但实际上不是。

Python虚拟机在执行def语句时,会动态创建一个函数,即PyFunctionObject,在这个过程中,MAKE_FUNCTION指令时一个关键:

ceval.c

case MAKE_FUNCTION:
	v = POP(); //获得与函数f对应的PyCodeObject
	x = PyFunction_New(v, f->f_globals);
	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;

  

在MAKE_FUNCTION之前,Python虚拟机会执行"0   LOAD_CONST   0",将与函数f对应的PyCodeObject对象压入到运行时栈。在执行MAKE_FUNCTION时,再将其取出,然后以该对象和当前PyFrameObject对象中维护的global名字空间f_globals对象为参数,通过PyFunction_New创建一个新的PyFunctionObject对象,而这个f_globals,将成为函数f运行时栈时的global名字空间

funcobject.c

PyObject * PyFunction_New(PyObject *code, PyObject *globals)
{
	//[1]:申请PyFunctionObject对象所需的内存空间
	PyFunctionObject *op = PyObject_GC_New(PyFunctionObject, &PyFunction_Type);
	static PyObject *__name__ = 0;
	if (op != NULL) {
		……
		//设置PyCodeObject对象
		op->func_code = code;
		//设置global名字空间
		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_closure = NULL;
		//函数中的常量对象表
		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;
		……
	}
	else
		return NULL;
	_PyObject_GC_TRACK(op);
	return (PyObject *)op;
}

  

创建PyFunctionObject对象之后,MAKE_FUNCTION还会进行一些处理函数参数的动作,由于我们的f是最简单的无参函数,没有任何参数,所以参数的传递机制会在后面的章节剖析

在MAKE_FUNCTION结束之后,新建的PyFunctionObject对象通过PUSH操作压入运行时栈中,随后的"6   STORE_NAME   0"和"9   LOAD_NAME   0"不再详述。下面,图1-1展示了def f()语句执行完成后,运行时栈和local名字空间的情况:

图1-1创建PyFunctionObject对象后的虚拟机状态

函数调用

从"12   CALL_FUNCTION   0"开始,Python虚拟机就开始进行调用函数的动作了

ceval.c

case CALL_FUNCTION:
	{
		PyObject **sp;
		PCALL(PCALL_ALL);
		sp = stack_pointer;
		x = call_function(&sp, oparg);
		stack_pointer = sp;
		PUSH(x);
		if (x != NULL)
			continue;
		break;
	}

  

CALL_FUNCTION的实现非常简单,仅仅是取得栈顶指针,然后就调用call_function函数了。那么我们就跟随着Python虚拟机的步伐,进入call_function函数一探究竟

ceval.c

static PyObject * call_function(PyObject ***pp_stack, int oparg)
{
	//[1]:处理函数参数信息
	int na = oparg & 0xff;
	int nk = (oparg >> 8) & 0xff;
	int n = na + 2 * nk;
	//[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
			Py_INCREF(func);
		READ_TIMESTAMP(*pintr0);
		//[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;
}

  

可以看到,不光是Function,还有CFunction和Method也会进入call_function,这里的CFunction为内建函数或类方法,Method为实例方法。在这里,我们的重心还是在Function上,先不管CFunction和Method

在call_function的[1]处,有一些处理函数参数信息的动作,n指明了在运行时栈中,栈顶有多少个元素是与参数相关的。当然,对于我们的f,这里的na、nk和n都是0,所以[2]处的pfunc为(*pp_stack) - 1,因为pp_stack就是我们在CALL_FUNCTION的指令代码中传入的当前运行时栈的栈顶指针

很显然,*pFunc,也就是func,指向了Python虚拟机在CALL_FUNCTION之前通过"9   LOAD_NAME   0"指令压入到运行时栈中的那个在MAKE_FUNCTION中创建的PyFunctionObject对象

Python虚拟机代码在通过[3]处的检查,就会进入fast_function。这里,我们也进入fast_function函数

ceval.c

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);
		……
		//再次进入Python虚拟机
		retval = PyEval_EvalFrameEx(f, 0);
		……
		return retval;
	}
	if (argdefs != NULL)
	{
		d = &PyTuple_GET_ITEM(argdefs, 0);
		nd = ((PyTupleObject *)argdefs)->ob_size;
	}
	//PyEval_EvalCodeEx的实现中,依然会调用PyEval_EvalFrameEx
	return PyEval_EvalCodeEx(co, globals,
							 (PyObject *)NULL, (*pp_stack) - n, na,
							 (*pp_stack) - 2 * nk, nk, d, nd,
							 PyFunction_GET_CLOSURE(func));
}

  

进入fast_function之后,首先会从PyFunctionObject对象中抽取出之前保存的PyCodeObject对象及函数运行时的global名字空间等信息。然后我们可以看到fast_function的执行分两条路径,无参函数会在fast_function的[1]处作出判断,进入函数的快速通道,Python虚拟机会为其创建一个新的PyFrameObject对象,进而调用PyEval_EvalFrameEx

而在另一条路上,则是PyEval_EvalCodeEx,函数PyEval_EvalCodeEx包含一系列复杂的动作,但如果追踪下去,它最后依旧要调用PyEval_EvalFrameEx函数完成字节码指令的执行。从PyEval_EvalFrameEx开始,Python虚拟机才真正开始进入函数调用的状态。这个过程实际上就是对x86平台上函数调用的模拟,在调用函数时创建新的栈帧,在新栈帧中执行函数代码。

有一点需要注意,在最终通过PyEval_EvalFrameEx时,PyFunctionObject对象的影响已经消失了,真正对栈帧产生影响的是PyFunctionObject中存储的PyCodeObject对象和global名字空间。说白了,PyFunctionObject只是为调用PyEval_EvalFrameEx时运输PyCodeObject对象和global名字空间

之前我们有提到函数的快速通道,那什么样的函数才可以进入快速通道呢?像C、C++和Java这样的函数就可以,也就是形如:func(x, y)这样的函数,如果像Python独有的函数func(a, *args, **kwargs)这样的函数则不行,Python正是靠函数参数的形式决定是否可以进入快速通道的

原文地址:https://www.cnblogs.com/beiluowuzheng/p/9515473.html

时间: 2024-11-08 13:30:52

Python虚拟机之函数机制(一)的相关文章

Python虚拟机之函数机制(二)

函数执行时的名字空间 在Python虚拟机之函数机制(一)这一章中,我们对Python中的函数调用机制有个大概的了解,在此基础上,我们再来看一些细节上的问题.在执行MAKE_FUNCTION指令时,调用了PyFunction_New方法,这个方法有一个参数是globals,这个globals最终将称为与函数f对应的PyFrameObject中的global名字空间--f_globals ceval.c case MAKE_FUNCTION: v = POP(); /* code object *

Python虚拟机之函数机制(三)

参数类别 我们在Python虚拟机之函数机制(一)和Python虚拟机之函数机制(二)这两个章节中,分别PyFunctionObject对象和函数执行时的名字空间.本章,我们来剖析一下函数参数的实现. 在Python中,函数的参数根据形势的不同可以分为四种类别: 位置参数:如f(a, b),a和b称为位置参数 键参数:f(a, b, name="Python"),其中的name="Python"被称为键参数 扩展位置参数:f(a, b, *args),其中*args

《python解释器源码剖析》第12章--python虚拟机中的函数机制

12.0 序 函数是任何一门编程语言都具备的基本元素,它可以将多个动作组合起来,一个函数代表了一系列的动作.当然在调用函数时,会干什么来着.对,要在运行时栈中创建栈帧,用于函数的执行. 在python中,PyFrameObject对象就是一个对栈帧的模拟,所以我们即将也会看到,python虚拟机在执行函数调用时会动态地创建新的PyFrameObject对象.随着函数调用链的增长,这些PyFrameObject对象之间也会形成一条PyFrameObject对象链,这条链就是对象x86平台上运行时栈

python虚拟机

翻译自<Python Virtual Machine> Python 虚拟机 每个函数对象都和以下的三个结构: 1.包含参数的局部变量名称(in .__code__.varnames) 2.全局变量名称(in .__code__.co_names) 3.常数(in .__code__.co_consts) 在python定义函数的时候创建这些结构,它们被定义在函数对应的__code__对象. 如果我们定义如下: Def minimum(alist): m=None if let(alist)

《python源码剖析》笔记 python虚拟机中的函数机制

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.Python虚拟机在执行函数调用时会动态地创建新的 PyFrameObject对象, 这些PyFrameObject对象之间会形成PyFrameObject对象链,模拟x86平台上运行时栈 2.PyFuctionObject对象 typedef struct { PyObject_HEAD PyObject *func_code: //对应函数编译后的PyCodeObject对象 Py

Python虚拟机函数机制之闭包和装饰器(七)

函数中局部变量的访问 在完成了对函数参数的剖析后,我们再来看看,在Python中,函数的局部变量时如何实现的.前面提到过,函数参数也是一种局部变量.所以,其实局部变量的实现机制与函数参数的实现机制是完全一样的.这个"一样"是什么意思呢? 之前我们剖析过Python虚拟机的一些指令,如果要访问一个变量,应该使用LOAD_NAME指令,应该依照local.global.builtin这三个名字空间里去检索变量名所对应的变量值.然后在调用函数时,Python虚拟机通过PyFrame_New创

Python虚拟机类机制之自定义class(四)

用户自定义class 在本章中,我们将研究对用户自定义class的剖析,在demo1.py中,我们将研究单个class的实现,所以在这里并没有关于继承及多态的讨论.然而在demo1.py中,我们看到了许多类的内容,其中包括类的定义.类的构造函数.对象的实例化.类成员函数的调用等 demo1.py class A(object): name = "Python" def __init__(self): print("A::__init__") def f(self):

《python解释器源码剖析》第13章--python虚拟机中的类机制

13.0 序 这一章我们就来看看python中类是怎么实现的,我们知道C不是一个面向对象语言,而python却是一个面向对象的语言,那么在python的底层,是如何使用C来支持python实现面向对象的功能呢?带着这些疑问,我们下面开始剖析python中类的实现机制.另外,在python2中存在着经典类(classic class)和新式类(new style class),但是到Python3中,经典类已经消失了.并且python2官网都快不维护了,因此我们这一章只会介绍新式类. 13.1 p

python学习总结(函数进阶)

-------------------程序运行原理------------------- 1.模块的内建__name__属性,主模块其值为__main__,导入模块其值为模块名 1.创建时间,py文件比pyc文件新,则从新生成pyc. 2.magic num,做运行前版本测试,版本不同重新生成pyc. 3.PyCodeObject对象,源代码中的字符串,常量值,字节码指令,原始代码行号的对应关系. 2.LEGB规则 1.Local :本地 当前所在命名空间(如函数,模块),函数的参数也属于命名空