zoukankan      html  css  js  c++  java
  • 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
    ", f->f_globals);
    		//打印globals名字空间的内容
    		printf("[MAKE_FUNCTION]:");
    		PyFile_WriteObject(f->f_globals, w, Py_PRINT_RAW);
    		printf("
    ");
    		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
    ", func_globals);
    		//打印globals名字空间的内容
    		printf("[call_function]:");
    		PyFile_WriteObject(func_globals, std, Py_PRINT_RAW);
    		printf("
    ");
    	}
    	
    	……
    }
    

      

    然后,我们执行一下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源代码对应的字节码指令

    [root@10-19-127-65 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对象了

  • 相关阅读:
    常用Dos 命令
    Expect: 100continue
    Sql Server 文件和文件组体系结构
    Build Action
    regasm.exe 程序集注册工具
    获得user account的SID,GUID
    sp_change_users_login
    Regsvr32
    ASP.NET IIS 注册工具 (Aspnet_regiis.exe)
    随机生成300道四则运算
  • 原文地址:https://www.cnblogs.com/beiluowuzheng/p/9525445.html
Copyright © 2011-2022 走看看