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   
    

    和预测相符。




  • 相关阅读:
    3、Linux知识点/dos基础命令
    2、进制转换
    1、软件测试基础####################################################
    50.React跳转路由传参3种方法和区别
    49.react中使用less
    48.vue-awesome-swipe使用
    47、安装node-sass后运行报错
    46、VUE + JS 面试宝典
    45、导航钩子函数中使用next()和next('指定路径')的区别:
    44、css实现水波纹效果
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3180229.html
Copyright © 2011-2022 走看看