python虚拟机详解_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) ==0 else Alist[0]

for v in alist[1:]:

if vim:

m = v

return m

我们得到

minimum.__code__.co_varnames is ('alist','m','v')

minimum.__code__.co_names is ('len','None')

minimum.__code__.co_consts is (None,0,1)

用于索引的数字+load 运算符(LOAD_FAST、LOAD_GLOBAL、LOAD_CONST都会在之后讨论)。

在PVM中主要的数据结构式“regular”栈(由一连串的push、pop组成)。对栈的主要操作就是load/push和store/pop。我们在栈顶load/push一个值,栈向上扩展,伴随着栈指针上移指向栈顶。同样,store/pop一个栈顶值时,栈指针下移。

还有一个次要的block栈用于从循环嵌套、try和指令中取出值。比如,一个断点指令在block栈中被编码,用于判断哪个循环块被n断下(并如何继续执行循环外的指令)。当循环,try/except和指令开始执行时,他们的信息被push到block栈上;当它们结束时从堆栈上弹出。这种块block stack对于现在来说太过麻烦,不必要去理解:所以当我们遇到有关block stack 的指令时,会指出将其忽略的原因。

这儿有个有关栈操作的简单例子,计算 d=a+b*c。假设a、b、c、d都是一个函数中的局部变量:co_varnames =('a','b','c','d')且这些符号对应的实际值被存放在并行元组中:(1,2,3,none)。符号在元组中的位置与其值在元组的位置是一一对应的。

LOAD_FAST N

load/push 将co_varnames[N]对应的值压入栈,stackp+=1,stack[stackp] = co_varnames[N]

STORE_FAST N

store/pop 将栈顶的值放入co_varnames[N], co_varnames[N] = stack[stackp], stackp-=1

BINARY_MULTIPLY

将‘*’的两个运算数压入栈,stack[stackp-1]=stack[stackp-1]*stack[stack];stackp-=1(将栈顶的两个值转化为它们的乘积)

BINARY_ADD

将‘+’的两个运算数压入栈,stack[stackp-1]=stack[stackp-1]+stack[stack];stackp-=1(将栈顶的两个值转化为它们的和)

d = a+b*c 的PVM code:

LOAD_FAST 0

LOAD_FAST 1

LOAD_FAST 2

BINARY_MULTIPLY

BINARY_ADD

STORE_FAST 3

初始状态:

co_varnames =('a','b','c','d')

values=(1,2,3,none)

+--------------------+

3 | |

+--------------------+

2 | |

+--------------------+

1 | |

+--------------------+

0 | |

+--------------------+

stack (with stackp=-1,it is an empty stack)

LOAD_FAST 0:

+--------------------+

3 | |

+--------------------+

2 | |

+--------------------+

1 | |

+--------------------+

0 | 1: value of a |

+--------------------+

stack(with stackp=0)

LOAD_FAST 1:

+--------------------+

3 | |

+--------------------+

2 | |

+--------------------+

1 | 2: value of b |

+--------------------+

0 | 1: value of a |

+--------------------+

stack (with stackp=1)

LOAD_FAST 2:

+--------------------+

3 | |

+--------------------+

2 | 3: value of c |

+--------------------+

1 | 2: value of b |

+--------------------+

0 | 1: value of a |

+--------------------+

stack (with stackp=2)

BINARY_MULTIPLY:

+--------------------+

3 | |

+--------------------+

2 | |

+--------------------+

1 | 6: value of b*c |

+--------------------+

0 | 1: value of a |

+--------------------+

stack (with stackp=1)

BINARY_ADD:

+--------------------+

3 | |

+--------------------+

2 | |

+--------------------+

1 | |

+--------------------+

0 | 7: value of a+b*c |

+--------------------+

stack (with stackp=0)

STORE_FAST 3:

+--------------------+

3 | |

+--------------------+

2 | |

+--------------------+

1 | |

+--------------------+

0 | |

+--------------------+

stack (with stackp=-1)

co_varnames =('a','b','c','d')

values=(1,2,3,7)

PVM的控制流

在PVM的每个指令都包含了1~3字节的信息。第一个字节是操作标识或字节码,后面的两字节是字节码的操作数(但并不是所有的字节码都需要操作数:BINARY_ADD就不需要)。两字节能够表示0~65536:所以python的函数中不能有超过65536个不同的局部变量。

指令被储存在内存中:把内存也看作一种储存有次序的数据的列表结构。

Memory Instruction

Location

0 LOAD_FAST 0

3 LOAD_FAST 1

6 LOAD_FAST 2

9 BINARY_MULTIPLY

10 BINARY_ADD

11 STORE_FAST 3

把内存列表命名为m

第一条指令被存储在m[0],后一指令存储在高3或高1的位置处(占3字节:有些指令有明确操作数的:load/store。有些指令有隐含的操作数:stack 、pc。占1字节:没有操作数的指令:binary运算)

一旦这些指令被加载进内存后,PVM按照一个简单的规则执行他们。执行周期赋予了计算机生命,这是计算机科学的基础。

(1)从m [pc]开始获取操作及其操作数(如果存在)

(2)pc + = 3(如果操作数存在)或pc + = 1(如果没有操作数存在)

(3)执行操作码(可能更改其操作数,堆栈,堆栈或pc)

(4)转到步骤1

一些运算会操作stack/stackp和存变量值的元组,一些会改变pc(比如jump指令)。

所以pc初始时0,PVM执行上述代码以以下流程:

1.获取操作m [0],操作数m [1]和m [2]

2.将pc递增至3

3.操纵堆栈(见上文)

4.回到步骤1

1.取m [3]的操作,m [4]和m [5]

2.将pc增加到6

3.操纵堆栈(见上文)

4.回到步骤1

1.取m [6]和m [7]和m [8]的操作数,

2.将pc增加到9

3.操纵堆栈(见上文)

4.回到步骤1

1.获取操作a m [9]:它没有操作数

2.将pc增加到10

3.操纵堆栈(见上文)

4.回到步骤1

1.获取操作m [10]:它没有操作数

2.将pc增加到11

3.操纵堆栈(见上文)

4.回到步骤1

内存中指向此处时,没有代码可以执行。在下一个例子中我们可以看到PVM如何执行一个更复杂的代码。

如简要介绍的那样,我们可以用dis.py模块中使用dis函数打印任何Python函数(和模块/类也可以)的注释描述;这里我们打印函数。

def addup(alist):

sum=0

for v in alist:

sum = sum + v

return sum

这个例子用来显示一般函数对象的有用的信息(它的名称,它的三个元组,和反编译信息)

def func_obj(fo):

print(fo.__name__)

print(' co_varnames:',fo.__code__.co_varnames)

print(' co_names :',fo.__code__.co_names)

print(' co_consts :',fo.__code__.co_consts,'\n')

print('Source Line m operation/byte-code operand (useful name/number)\n'+69*'-')

dis.dis(fo)

calling func_obj(addup) prints

addup

co_varnames: ('alist', 'sum', 'v')

co_names : ()

co_consts : (None, 0)

Source Line m op/byte-code operand (useful name/number)

---------------------------------------------------------------------

2 0 LOAD_CONST 1 (0)

3 STORE_FAST 1 (sum)

3 6 SETUP_LOOP 24 (to 33)

9 LOAD_FAST 0 (alist)

12 GET_ITER

>> 13 FOR_ITER 16 (to 32)

16 STORE_FAST 2 (v)

4 19 LOAD_FAST 1 (sum)

22 LOAD_FAST 2 (v)

25 BINARY_ADD

26 STORE_FAST 1 (sum)

29 JUMP_ABSOLUTE 13

>> 32 POP_BLOCK

5 >> 33 LOAD_FAST 1 (sum)

36 RETURN_VALUE

有>>标识的行说明有其他指令会jump到此行。

更详细的描述:

第2行:

m [0]:在堆栈上加载值0(co_consts [1])

m [3]:将值0存入sum(co_varnames [1])

第3行:

m [6]:通过将循环块的大小压入栈来设置循环

m [9]:从栈中加载alist(co_varnames [0])的值

m [12]:通过迭代器替换堆栈上的值(通过弹出和推送)

m [13]:在堆栈中加载下一个迭代器值,如果StopIteration引起,则跳转到m [32]

(m [29]中的代码跳回此位置进行循环)

m [16]:将下一个值存储到v(co_varnames [2])中,将其从堆栈中弹出

第4行:

m [19]:在栈中加载sum(co_varnames [1])的值

m [22]:将v(co_varnames [2])的值加载到栈上

m [25]:将栈顶的两个值进行相加操作后,将结果加载到栈上

m [26]:栈顶弹出值,存储在sum(co_varnames [1])中

m [29]:将pc设置为13,因此在m [13]中执行的下一条指令

(跳回到前一个位置使循环循环)

m [32]:弹出m[6]对循环块的设置而压入栈的值

(m [13]中的代码在这里跳转到StopIteration,终止循环)

第5行:

m [33]:将sum(co_varnames [1])的值压入栈,用于返回

m [36]:从函数返回结果在堆栈顶部

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值