zoukankan      html  css  js  c++  java
  • (Python学习9)Python虚拟机中的一般表达式

    1、准备工作

    执行.py程序时,Python解释器对PyCodeObject的co_code存储的字节码进行解释执行,同时co_consts存储了常量,co_names存储了变量名称。用compile()可将.py编译为PyCodeObject,dis模块可对PyCodeObject的字节码反编译。
    构建工具co_dist.py:

    source = open('test.py').read()
    co = compile(source, 'test.py', 'exec')
    print("const	:	", co.co_consts)
    print("name	:	", co.co_names)
    
    import dis
    dis.dis(co)

    测试程序test.py:

    i = 1
    s = "efei"
    d = {}
    l = []

    执行co_dist可得结果:

    const	:	 (1, 'efei', None)
    name	:	 ('i', 's', 'd', 'l')
      1           0 LOAD_CONST               0 (1) 
                  3 STORE_NAME               0 (i) 
    
    
      2           6 LOAD_CONST               1 ('efei') 
                  9 STORE_NAME               1 (s) 
    
    
      3          12 BUILD_MAP                0 
                 15 STORE_NAME               2 (d) 
    
    
      4          18 BUILD_LIST               0 
                 21 STORE_NAME               3 (l) 
                 24 LOAD_CONST               2 (None) 
                 27 RETURN_VALUE 

    可看到consts存储的对象,names存储的值,并且都是以PyTupleObject存储的。
    紧接着是字节码的反编译结果,从左只右依次表示:源代码中行号、co_code中字节序号、汇编指令、参数、以及参数代表的含义。

    以分析框架为主,如无必要,对汇编指令对应的函数不再具体分析。
    ---------------------------------------------------------------------------------------------

    2、简单对象创建

    看第一条 i = 1:

      1           0 LOAD_CONST               0 (1) 
                  3 STORE_NAME               0 (i) 

    其中:

    case LOAD_CONST:
    	x = GETITEM(consts, oparg);
    	Py_INCREF(x);
    	PUSH(x);
    	
    #define GETITEM(v, i) PyTuple_GetItem((v), (i))

    oparg即为传入的参数0。所以LOAD_CONST 0 解释为:从consts中取出第0个对象x,将x压入运行栈中。

    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;
    	}

    STORE_NAME 0 的解释为:从名为names中取出第0个对象w,运行时栈中弹出v,将(w, v)放入_dict对象f_locals中,即局部变量。
    这两句汇编指令运行的状态图如下:


     2           6 LOAD_CONST               1 ('efei') 
                  9 STORE_NAME               1 (s) 

    运行时的状态图:

      3          12 BUILD_MAP                0 
                 15 STORE_NAME               2 (d) 
    
    
      4          18 BUILD_LIST               0 
                 21 STORE_NAME               3 (l) 
                 24 LOAD_CONST               2 (None) 
                 27 RETURN_VALUE 

    类似,只不过BUILD_MAP创建一个_dict对象,BUILD_LIST创建一个_list对象。

    case BUILD_MAP:
    	x = _PyDict_NewPresized((Py_ssize_t)oparg);
    	PUSH(x);
    case BUILD_LIST:
    	x =  PyList_New(oparg);
    	if (x != NULL) {
    	       for (; --oparg >= 0;) {
    			w = POP();
    			PyList_SET_ITEM(x, oparg, w);
    		}
    	PUSH(x);

    BUILD_MAP 直接创建一个空对象,然后入栈;BUILD_LIST创建一个对象之后会将前面oparg栈中的对象弹出放入_list对象中,然后将_list对象入栈。
    最后两条语句需注意,将返回值(Node)压入栈中,供ETURN_VALUE调用。
    最终如下:

    3、复杂内建对象创建

    i = 1
    s = "efei"
    d = {"1":1, "2":2}
    l = [1, 2]
    const	:	 (1, 'efei', '1', 2, '2', None)
    name	:	 ('i', 's', 'd', 'l')
      1           0 LOAD_CONST               0 (1) 
                  3 STORE_NAME               0 (i) 
    
    
      2           6 LOAD_CONST               1 ('efei') 
                  9 STORE_NAME               1 (s) 
    
    
      3          12 BUILD_MAP                2 				// 新建空_dict,并压入栈
                 15 LOAD_CONST               0 (1) 			// 将第一个元素的key压入栈
                 18 LOAD_CONST               2 ('1') 		// 将第一个元素的val压入栈
                 21 STORE_MAP            					// 将第一个元素放入_dict
                 22 LOAD_CONST               3 (2) 			// 第二个元素……
                 25 LOAD_CONST               4 ('2')		 		
                 28 STORE_MAP            
                 29 STORE_NAME               2 (d) 			// 将_dict放入f_locals
    
    
      4          32 LOAD_CONST               0 (1) 			// 将list中的元素压入栈
                 35 LOAD_CONST               3 (2) 			// ……
                 38 BUILD_LIST               2 				// 创建_list,并将栈中的前2个对象放入_list
                 41 STORE_NAME               3 (l) 			// 将_list放入f_locals
                 44 LOAD_CONST               5 (None) 
                 47 RETURN_VALUE         

    如上,需注意 d = {"1":1, "2":2} 的汇编指令与《Python源码剖析》中不同,书中用的Python2.5,本处用的Python3.3,其实Python2.7.5也是上述指令。

    case STORE_MAP:
    	w = TOP();     /* key */
    	u = SECOND();  /* value */
    	v = THIRD();   /* dict */
    	STACKADJ(-2);
    	assert (PyDict_CheckExact(v));
    	err = PyDict_SetItem(v, w, u);  /* v[w] = u */
    	Py_DECREF(u);
    	Py_DECREF(w);

    比Pyhotn2.5逻辑更清晰,汇编指令更清晰了,STORE_MAP的操纵也更清晰了。

    4、其他

    a = 5
    b = a
    c = a + b
    print(c)
    const	:	 (5, None)
    name	:	 ('a', 'b', 'c', 'print')
      1           0 LOAD_CONST               0 (5) 
                  3 STORE_NAME               0 (a) 
    
    
      2           6 LOAD_NAME                0 (a)		// 在名字空间中搜索a,并将其值压入栈
                  9 STORE_NAME               1 (b) 
    
    
      3          12 LOAD_NAME                0 (a) 
                 15 LOAD_NAME                1 (b) 
                 18 BINARY_ADD           				// 相加,将结果压入栈
                 19 STORE_NAME               2 (c) 
    
    
      4          22 LOAD_NAME                3 (print) 	// 搜索并压入函数对象
                 25 LOAD_NAME                2 (c) 		// 搜索并压入C对应的对象
                 28 CALL_FUNCTION            1 (1 positional, 0 keyword pair) 
                 31 POP_TOP              
                 32 LOAD_CONST               1 (None) 
                 35 RETURN_VALUE         


    b = a 的汇编指令为 LOAD_NAME 与 STORE_NAME。LOAD_NAME实质为,依次在f_locals、f_globals、f_builtins中搜索,如果找到则将相应的对象压入栈中,找不到会终止解释器运行。(其实栈中存储的均为对象的指针……)

    执行后,已经将具体值赋给了b,图示如下:


    BINARY_ADD 实质为处理对象相加的函数。需注意:若两对象都是PyStringObject或PyIntObject,处理速度较快,否则会调用其他函数处理速度较慢。

    print的汇编指令也与书中不同,Python3.x中print已经变作一个函数,也是一种对象,可以将其指针压入栈中。可以推测,CALL_FUNCTION会通过传入的参数来分辨函数指针与该函数需用的参数。CALL_FUNCTION最终会将执行结果压入栈中,故调用结束时还需弹出。

    为验证推测,添加语句print(a,b)汇编指令会添加

      5          32 LOAD_NAME                3 (print) 
                 35 LOAD_NAME                0 (a) 
                 38 LOAD_NAME                1 (b) 
                 41 CALL_FUNCTION            2 (2 positional, 0 keyword pair) 
                 44 POP_TOP   
    

    和预测相符。




  • 相关阅读:
    使用 yo 命令行向导给 SAP UI5 应用添加一个新的视图
    SAP Fiori Elements 应用的 manifest.json 文件运行时如何被解析的
    SAP UI5 标准应用的多语言支持
    微软 Excel 365 里如何设置下拉菜单和自动高亮成指定颜色
    SAP Fiori Elements 应用里的 Title 显示的内容是从哪里来的
    本地开发好的 SAP Fiori Elements 应用,如何部署到 ABAP 服务器上?
    如何在 Cypress 测试代码中屏蔽(Suppress)来自应用代码报出的错误消息
    教你一招:让集群慢节点无处可藏
    应用架构步入“无服务器”时代 Serverless技术迎来新发展
    MySQL数据库事务隔离性的实现
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3180229.html
Copyright © 2011-2022 走看看