函数执行时的名字空间

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

ceval.c

  1. case MAKE_FUNCTION:
  2. v = POP(); /* code object */
  3. x = PyFunction_New(v, f->f_globals);
  4. Py_DECREF(v);
  5. /* XXX Maybe this should be a separate opcode? */
  6. if (x != NULL && oparg > 0) {
  7. v = PyTuple_New(oparg);
  8. if (v == NULL) {
  9. Py_DECREF(x);
  10. x = NULL;
  11. break;
  12. }
  13. while (--oparg >= 0) {
  14. w = POP();
  15. PyTuple_SET_ITEM(v, oparg, w);
  16. }
  17. err = PyFunction_SetDefaults(x, v);
  18. Py_DECREF(v);
  19. }
  20. PUSH(x);
  21. break;

 

  1. # cat demo.py
  2. def f():
  3. print("Function")
  4.  
  5. f()
  6.  
  7. # python2.5
  8. ……
  9. >>> source = open("demo.py").read()
  10. >>> co = compile(source, "demo.py", "exec")
  11. >>> import dis
  12. >>> dis.dis(co)
  13. 1 0 LOAD_CONST 0 (<code object f at 0x7fd9831c3648, file "demo.py", line 1>)
  14. 3 MAKE_FUNCTION 0
  15. 6 STORE_NAME 0 (f)
  16.  
  17. 5 9 LOAD_NAME 0 (f)
  18. 12 CALL_FUNCTION 0
  19. 15 POP_TOP
  20. 16 LOAD_CONST 1 (None)
  21. 19 RETURN_VALUE
  22. >>> from demo import f
  23. Function
  24. >>> dis.dis(f)
  25. 2 0 LOAD_CONST 1 ('Function')
  26. 3 PRINT_ITEM
  27. 4 PRINT_NEWLINE
  28. 5 LOAD_CONST 0 (None)
  29. 8 RETURN_VALUE
  30.   
  31.   

  

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

  1. case MAKE_FUNCTION:
  2. v = POP(); /* code object */
  3. char *v_co_name = PyString_AsString(((PyCodeObject *)v)->co_name);
  4. if(strcmp(v_co_name, "f") == 0)
  5. {
  6. if (stream == NULL || stream == Py_None)
  7. {
  8. w = PySys_GetObject("stdout");
  9. if (w == NULL)
  10. {
  11. PyErr_SetString(PyExc_RuntimeError,
  12. "lost sys.stdout");
  13. err = -1;
  14. }
  15.  
  16. }
  17. //打印globals名字空间的地址
  18. printf("[MAKE_FUNCTION]:f_globals addr:%p\n", f->f_globals);
  19. //打印globals名字空间的内容
  20. printf("[MAKE_FUNCTION]:");
  21. PyFile_WriteObject(f->f_globals, w, Py_PRINT_RAW);
  22. printf("\n");
  23. stream = NULL;
  24. }
  25. x = PyFunction_New(v, f->f_globals);
  26. ……
  27.  
  28. ……
  29. static PyObject * call_function(PyObject ***pp_stack, int oparg)
  30. {
  31. int na = oparg & 0xff;
  32. int nk = (oparg>>8) & 0xff;
  33. int n = na + 2 * nk;
  34. PyObject **pfunc = (*pp_stack) - n - 1;
  35. PyObject *func = *pfunc;
  36. PyObject *x, *w;
  37.  
  38. char *func_name = PyEval_GetFuncName(func);
  39. if (strcmp(func_name, "f") == 0)
  40. {
  41. PyObject *std = PySys_GetObject("stdout");
  42. //获取函数所对应的global名字空间
  43. PyObject *func_globals = (PyCodeObject *)PyFunction_GET_GLOBALS(func);
  44. //打印globals名字空间的地址
  45. printf("[call_function]:func_globals addr:%p\n", func_globals);
  46. //打印globals名字空间的内容
  47. printf("[call_function]:");
  48. PyFile_WriteObject(func_globals, std, Py_PRINT_RAW);
  49. printf("\n");
  50. }
  51.  
  52. ……
  53. }

  

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

  1. # cat demo1.py
  2. a = 1
  3. b = 3
  4.  
  5. def f():
  6. print("Function f")
  7.  
  8. def g():
  9. print("Function g")
  10.  
  11. f()
  12.  
  13. # python2.5 demo1.py
  14. [MAKE_FUNCTION]:f_globals addr:0x2237740
  15. [MAKE_FUNCTION]:{'a': 1, 'b': 3, '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'demo1.py', '__name__': '__main__', '__doc__': None}
  16. [call_function]:func_globals addr:0x2237740
  17. [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}
  18. 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源代码对应的字节码指令

  1. [root@10-19-127-65 python]# python2.5
  2. ……
  3. >>> source = open("demo1.py").read()
  4. >>> co = compile(source, "demo1.py", "exec")
  5. >>> import dis
  6. >>> dis.dis(co)
  7. 1 0 LOAD_CONST 0 (1)
  8. 3 STORE_NAME 0 (a)
  9.  
  10. 2 6 LOAD_CONST 1 (3)
  11. 9 STORE_NAME 1 (b)
  12.  
  13. 5 12 LOAD_CONST 2 (<code object f at 0x7f5d0aa74648, file "demo1.py", line 5>)
  14. 15 MAKE_FUNCTION 0
  15. 18 STORE_NAME 2 (f)
  16.  
  17. 9 21 LOAD_CONST 3 (<code object g at 0x7f5d0aa74918, file "demo1.py", line 9>)
  18. 24 MAKE_FUNCTION 0
  19. 27 STORE_NAME 3 (g)
  20.  
  21. 13 30 LOAD_NAME 2 (f)
  22. 33 CALL_FUNCTION 0
  23. 36 POP_TOP
  24. 37 LOAD_CONST 4 (None)
  25. 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

  1. case STORE_NAME:
  2. w = GETITEM(names, oparg);
  3. v = POP();
  4. if ((x = f->f_locals) != NULL)
  5. {
  6. if (PyDict_CheckExact(x))
  7. err = PyDict_SetItem(x, w, v);
  8. else
  9. err = PyObject_SetItem(x, w, v);
  10. Py_DECREF(v);
  11. if (err == 0)
  12. continue;
  13. break;
  14. }
  15. PyErr_Format(PyExc_SystemError,
  16. "no locals found when storing %s",
  17. PyObject_REPR(w));
  18. break;

  

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

Python虚拟机函数机制之名字空间(二)的更多相关文章

  1. Python虚拟机函数机制之位置参数的默认值(五)

    位置参数的默认值 在Python中,允许函数的参数有默认值.假如函数f的参数value的默认值是1,在我们调用函数时,如果传递了value参数,那么f调用时value的值即为我们传递的值,如果调用时没 ...

  2. Python虚拟机函数机制之参数类别(三)

    参数类别 我们在Python虚拟机函数机制之无参调用(一)和Python虚拟机函数机制之名字空间(二)这两个章节中,分别PyFunctionObject对象和函数执行时的名字空间.本章,我们来剖析一下 ...

  3. Python虚拟机函数机制之扩展位置参数和扩展键参数(六)

    扩展位置参数和扩展键参数 在Python虚拟机函数机制之参数类别(三)的例3和例4中,我们看到了使用扩展位置参数和扩展键参数时指示参数个数的变量的值.在那里,我们发现在函数内部没有使用局部变量时,co ...

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

    函数中局部变量的访问 在完成了对函数参数的剖析后,我们再来看看,在Python中,函数的局部变量时如何实现的.前面提到过,函数参数也是一种局部变量.所以,其实局部变量的实现机制与函数参数的实现机制是完 ...

  5. Python虚拟机函数机制之无参调用(一)

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

  6. Python虚拟机函数机制之位置参数(四)

    位置参数的传递 前面我们已经分析了无参函数的调用过程,我们来看看Python是如何来实现带参函数的调用的.其实,基本的调用流程与无参函数一样,而不同的是,在调用带参函数时,Python虚拟机必须传递参 ...

  7. Python虚拟机类机制之填充tp_dict(二)

    填充tp_dict 在Python虚拟机类机制之对象模型(一)这一章中,我们介绍了Python的内置类型type如果要完成到class对象的转变,有一个重要的步骤就是填充tp_dict对象,这是一个极 ...

  8. Python虚拟机类机制之instance对象(六)

    instance对象中的__dict__ 在Python虚拟机类机制之从class对象到instance对象(五)这一章中最后的属性访问算法中,我们看到“a.__dict__”这样的形式. # 首先寻 ...

  9. Python虚拟机之for循环控制流(二)

    Python虚拟机中的for循环控制流 在Python虚拟机之if控制流(一)这一章中,我们了解if控制流的字节码实现,在if控制结构中,虽然Python虚拟机会在不同的分支摇摆,但大体还是向前执行, ...

随机推荐

  1. js基础拖拽效果

    function drag(ele) { const config = { mark: 0, x: 0, y: 0, left: ele.offsetLeft, top: ele.offsetTop, ...

  2. wpf ComboBox的SelectionBoxItem相关依赖属性

    以前没有注意SelectionBoxItem相关依赖属性,这几天看wpf源码 特意研究了一番 <Style x:Key="ComboBoxStyle1" TargetType ...

  3. pixhawk 固件Firmware内执行make px4fmu-v2_default 编译报错解决办法

    执行下列指令报错 make px4fmu-v2_default /bin/sh: 1: Tools/check_cmake.sh: Permission denied Makefile:44: Not ...

  4. 9、数值的整数次方------------>剑指offer系列

    数值的整数次方 给定一个double类型的浮点数base和int类型的整数exponent.求base的exponent次方. 思路 这道题逻辑上很简单,但很容易出错 关键是要考虑全面,考虑到所有情况 ...

  5. PHP的加解密:如何安装ioncube扩展?

    一.下载loader-wizard.php(支持php5.3.php5.4.php5.5.php5.6版本) ioncube提供了一个安装的向导程序,可以非常方便的帮助检测php的运行环境,自动给出提 ...

  6. SQL Server数据库log shipping 灾备(Part1 )

    1.概述 Log Shipping为SQL Server提供的数据库备份过程.它可以将数据库整个复制到另一台服务器上.在这种情况下,交易日志也会定期发送到备份服务器上供恢复数据使用,这使得服务器一直处 ...

  7. 记一次RabbitMq 安装和配置坑

    记一次RabbitMq 安装和配置坑 正常情况下安装 先安装erl ,在安装rabbitmq 这个在windows下的安装没什么技巧,按照默认一路下一步就ok.安装好后可以到cmd测试是否安装好. 测 ...

  8. python基础教程总结3—字典

    1.字典 1.1 字典类型与序列类型的区别: 存取和访问数据的方式不同. 序列类型只用数字类型的键(从序列的开始按数值顺序索引): 映射类型可以用其他对象类型作键(如:数字.字符串.元祖,一般用字符串 ...

  9. 【Orange Pi Lite2】 ——1《如何开始使用开源硬件》

    [Orange Pi Lite2] --1<如何开始使用开源硬件> 本文只在博客园发布 在开始前你需要准备的材料与软件 用户手册_Orange Pi Lite2 OrangePi_Lite ...

  10. 用NPOI操作EXCEL-锁定列CreateFreezePane()

    public void ExportPermissionRoleData(string search, int roleStatus) { var workbook = new HSSFWorkboo ...