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

函数执行时的名字空间

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

ceval.c

case MAKE_FUNCTION:
	v = POP(); /* code object */
	x = PyFunction_New(v, f->f_globals);
	Py_DECREF(v);
	/* XXX Maybe this should be a separate opcode? */
	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;

 

# 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
  
  

  

Python虚拟机中的一般表达式(三)中,我们介绍了LOAD_NAME这条指令,这条指令在执行时会依次从三个PyDictObject对象进行搜索,搜索顺序是:f_locals、f_globals、f_builtins。在PyFunction_New时传入的globals将成为在新的栈帧中执行函数的global名字空间。在MAKE_FUNCTION中,我们看到传入的globals参数为当前PyFrameObject对象中的f_globals。这意味着,在执行demo.py的字节码指令时的global名字空间,与执行函数f的字节码序列时的global名字空间实际上是同一个名字空间,这个名字空间通过PyFunctionObject的携带,和字节码指令对应的PyCodeObject对象一起被传入到新的栈帧中

下面,让我们修改MAKE_FUNCTION指令和call_function的实现,将global名字空间的地址和内容输出

ceval.c

case MAKE_FUNCTION:
	v = POP(); /* code object */
	char *v_co_name = PyString_AsString(((PyCodeObject *)v)->co_name);
	if(strcmp(v_co_name, "f") == 0)
	{
		if (stream == NULL || stream == Py_None)
		{
			w = PySys_GetObject("stdout");
			if (w == NULL)
			{
				PyErr_SetString(PyExc_RuntimeError,
								"lost sys.stdout");
				err = -1;
			}

		}
		//打印globals名字空间的地址
		printf("[MAKE_FUNCTION]:f_globals addr:%p\n", f->f_globals);
		//打印globals名字空间的内容
		printf("[MAKE_FUNCTION]:");
		PyFile_WriteObject(f->f_globals, w, Py_PRINT_RAW);
		printf("\n");
		stream = NULL;
	}
	x = PyFunction_New(v, f->f_globals);
	……

……
static PyObject * call_function(PyObject ***pp_stack, int oparg)
{
	int na = oparg & 0xff;
	int nk = (oparg>>8) & 0xff;
	int n = na + 2 * nk;
	PyObject **pfunc = (*pp_stack) - n - 1;
	PyObject *func = *pfunc;
	PyObject *x, *w;

	char *func_name = PyEval_GetFuncName(func);
	if (strcmp(func_name, "f") == 0)
	{
		PyObject *std = PySys_GetObject("stdout");
		//获取函数所对应的global名字空间
		PyObject *func_globals = (PyCodeObject *)PyFunction_GET_GLOBALS(func);
		//打印globals名字空间的地址
		printf("[call_function]:func_globals addr:%p\n", func_globals);
		//打印globals名字空间的内容
		printf("[call_function]:");
		PyFile_WriteObject(func_globals, std, Py_PRINT_RAW);
		printf("\n");
	}

	……
}

  

然后,我们执行一下demo1.py这个文件

# cat demo1.py
a = 1
b = 3

def f():
    print("Function f")

def g():
    print("Function g")

f()

# python2.5 demo1.py
[MAKE_FUNCTION]:f_globals addr:0x2237740
[MAKE_FUNCTION]:{‘a‘: 1, ‘b‘: 3, ‘__builtins__‘: <module ‘__builtin__‘ (built-in)>, ‘__file__‘: ‘demo1.py‘, ‘__name__‘: ‘__main__‘, ‘__doc__‘: None}
[call_function]:func_globals addr:0x2237740
[call_function]:{‘a‘: 1, ‘b‘: 3, ‘g‘: <function g at 0x7f54708c7de8>, ‘f‘: <function f at 0x7f54708c7b18>, ‘__builtins__‘: <module ‘__builtin__‘ (built-in)>, ‘__file__‘: ‘demo1.py‘, ‘__name__‘: ‘__main__‘, ‘__doc__‘: None}
Function f

  

可以看到,MAKE_FUNCTION中和call_function中的global名字空间的地址是一样的,demo1.py中所定义的符号都包含在global名字空间中,使得函数f可以使用a、b两个变量,正是依赖于global名字空间的传递,才使得函数f可以使用函数f以外的符号。现在,让我们分别看下[MAKE_FUNCTION]和[call_function]中的globals内容,我们会发现,前者没有函数f和g,后者有函数f和g,加上两者的地址是相同的。这说明在字节码指令执行的时候,一定会把函数f和g放入到global名字空间,否则,函数在global中找不到自身的定义,无法实现递归,虽然我们的函数f在这里并没有递归

那么,函数f和g是在何时偷偷溜进global名字空间呢?我们用dis模块来查看一下demo1.py源代码对应的字节码指令

[[email protected] python]# python2.5
……
>>> source = open("demo1.py").read()
>>> co = compile(source, "demo1.py", "exec")
>>> import dis
>>> dis.dis(co)
  1           0 LOAD_CONST               0 (1)
              3 STORE_NAME               0 (a)

  2           6 LOAD_CONST               1 (3)
              9 STORE_NAME               1 (b)

  5          12 LOAD_CONST               2 (<code object f at 0x7f5d0aa74648, file "demo1.py", line 5>)
             15 MAKE_FUNCTION            0
             18 STORE_NAME               2 (f)

  9          21 LOAD_CONST               3 (<code object g at 0x7f5d0aa74918, file "demo1.py", line 9>)
             24 MAKE_FUNCTION            0
             27 STORE_NAME               3 (g)

 13          30 LOAD_NAME                2 (f)
             33 CALL_FUNCTION            0
             36 POP_TOP
             37 LOAD_CONST               4 (None)
             40 RETURN_VALUE

  

我们看到"15   MAKE_FUNCTION   0"和"24   MAKE_FUNCTION   0"这两句指令,这两句都是执行def语句创建PyFunctionObject对象,MAKE_FUNCTION指令创建PyFunctionObject对象后便将其压入栈,显然,在global名字空间建立符号f和g与PyFunctionObject对象的映射不在MAKE_FUNCTION。所以我们往后找,这两句指令的后面又分别跟着"18   STORE_NAME   2 (f)"和"27   STORE_NAME   3 (g)",会不会是在这里建立符号与函数对象的映射呢?我们看看STORE_NAME的实现:

ceval.c

case STORE_NAME:
	w = GETITEM(names, oparg);
	v = POP();
	if ((x = f->f_locals) != NULL)
	{
		if (PyDict_CheckExact(x))
			err = PyDict_SetItem(x, w, v);
		else
			err = PyObject_SetItem(x, w, v);
		Py_DECREF(v);
		if (err == 0)
			continue;
		break;
	}
	PyErr_Format(PyExc_SystemError,
				 "no locals found when storing %s",
				 PyObject_REPR(w));
	break;

  

这里我们看到,STORE_NAME会对local名字空间做符号和其值的映射,但并不是我们之前所说的global名字空间啊!所以,到底是不是在这里做符号与函数的映射呢?答案是:符号与函数的映射,正是在STORE_NAME完成的。这里也暴露一个信息,demo1.py执行时对应的local名字空间和global名字空间实际上是一个对象,想想也是这个道理,因为函数的local名字空间存储的是函数内的局部变量,global存储的是函数之外的变量,那么一个脚本本身所对应的local名字空间存储的是脚本本身的变量,那么global名字空间呢?这里没得选,只能和脚本本身的local名字空间共同使用一个PyDictObject对象了

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

时间: 2024-10-31 08:30:08

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

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

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

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

PyFunctionObject对象 在Python中,任何一个东西都是对象,函数也不例外.函数这种抽象机制,是通过一个Python对象--PyFunctionObject来实现的 typedef struct { PyObject_HEAD PyObject *func_code; //编译后的PyCodeObject对象 PyObject *func_globals; //函数运行时的global名字空间 PyObject *func_defaults; //默认参数(tupple或NULL

Python基础九函数进阶(二)

回顾一下 函数名的本质就是函数的内存地址 1可以被引用 2可以当做容器类性的元素 3可以当做函数的参数和返回值 一.闭包 闭包的含义:内部函数引用外部作用域(非全局)的变量  (内部函数指的是函数内部定义的函数) 有与有了作用域的关系,我们就不能拿到函数内部的变量和函数了.如果我们有需求就是想拿到那怎么做呢?返回呀!我们都知道函数内的变量我们想要在函数外不用,可以直接返回这个变量,那么如果我们想在函数外部调用函数内部的函数呢? 是不是直接就把这个函数名字返回就好了呢? 这才是闭包函数最常用的方法

Python之内置函数(二)

4.13 内置函数二 内置函数 print(abs(-11))#绝对值函数--返回的都是正数 #结果为 11 enumerate--枚举("可迭代对象","序号的起始值")#默认的起始值是0 lst=[1,2] print([i for i in enumerate(lst,10)]) #结果为:[(10,1),(11,2)] print(max([1,2,5,7])) #求最大值 #结果为:7 print(min([1,2,3,33]))#求最小值 #结果为:1

Python基础—初识函数(二)

1.给函数参数增加元信息 写好一个函数,然后想为这个函数的参数增加一些额外的信息,这样的话其他使用者就能清楚的知道这个函数应该怎么使用. 使用函数参数注解是一个很好的办法,它能提示程序员应该怎样正确使用这个函数. 例如,下面有一个被注解了的函数: def add(x:int, y:int) -> int: return x + y python解释器不会对这些注解添加任何的语义.它们不会被类型检查,运行时跟没有加注解之前的效果也没有任何差距. 然而,对于那些阅读源码的人来讲就很有帮助啦.第三方

《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创