Python源码剖析-Python如何运行代码

原创 ryan007  2017-05-24 00:48  阅读 555 次

感觉这个题目有点大,我会尽我所能把这个过程描述清楚,就从我这个小白的角度来讲讲吧!如有描述不准确,不要吝惜你的笔墨,请指出来~

一,解释性语言和编译型语言的区别:

1,先了解一下解释器和编译器的区别请看前一篇介绍;

2,Python是一种解释性的语言;

 

二,什么是Python?

我们给Python下一个定义:Python是一种解释型、面向对象、动态数据类型的高级程序设计语言。所有的都是对象(object)。

Everything in Python is an object。

Python是由Guido van Rossum于1989年底发明,第一个公开发行版发行于1991年。 Python 源代码同样遵循 GPL(GNU General Public License)协议。

 

三,Python运行过程

1,核心结构

为什么要了解Python的运行过程呢?CPU不懂字符串呀,要想让CPU看懂我们编写的代码,她只能看懂二进制。要想了解Python的运行过程,首先了解Python的架构,我们看一下Python核心架构:

Python的核心架构分为三部分:

  1. 模块、类、自定义模块;
  2. 虚拟机运行;
  3. 运行时状态;

Python先把代码(.py文件)编译成字节码(scanner),交给字节码虚拟机(parser、compile),然后虚拟机一条一条执行字节码指令(code evauator),从而完成程序的执行。字节码在Python虚拟机程序里对应的是PyCodeObject对象。我们看一下PyCodeObject的结构:

typedef struct {
    PyObject_HEAD
    int co_argcount;        /* 位置参数个数 */
    int co_nlocals;         /* 局部变量个数 */
    int co_stacksize;       /* 栈大小 */
    int co_flags;   
    PyObject *co_code;      /* 字节码指令序列 */
    PyObject *co_consts;    /* 所有常量集合 */
    PyObject *co_names;     /* 所有符号名称集合 */
    PyObject *co_varnames;  /* 局部变量名称集合 */
    PyObject *co_freevars;  /* 闭包用的的变量名集合 */
    PyObject *co_cellvars;  /* 内部嵌套函数引用的变量名集合 */
    /* The rest doesn’t count for hash/cmp */
    PyObject *co_filename;  /* 代码所在文件名 */
    PyObject *co_name;      /* 模块名|函数名|类名 */
    int co_firstlineno;     /* 代码块在文件中的起始行号 */
    PyObject *co_lnotab;    /* 字节码指令和行号的对应关系 */
    void *co_zombieframe;   /* for optimization only (see frameobject.c) */
} PyCodeObject;

 

2,分析字节码

Python提供了内置函数compile可以编译Python代码和查看PyCodeObject对象,如下:

Python代码[test.py]

#test.py
#some function
def f(d=0):
    c=1
    print "hello"
a=9
b=8
f()

打开shell

>>> a=open('test.py','r').read()    #命令行模式中打开源文件进行编译
>>> co=compile(a,'src_file','exec')
>>> type(co)
<type 'code'>    #编译出了codeobject对象

查看感兴趣的信息:

>>> print co.co_names    #所有的符号名称
('f', 'a', 'b')
>>> print co.co_name    #模块名、函数名、类名
<module>
>>> print co.co_consts    #常量集合、函数f和两个int常量a,b,d
(0, <code object f at 0xb7273b18, file "src_file", line 2>, 9, 8, None)
>>> print co.co_consts[1].co_varnames    #可以看到f函数也是一个codeobject,打印f中的局部变量
('c',)
>>> print co.co_code    #字节码指令
dZdZdZedS
>>> print co.co_consts[1].co_firstlineno    #代码块在文件中的起始行号
2
>>> print co.co_stacksize    #代码栈大小
2
>>> print co.co_filename    #文件名
src_file    #模块名、函数名、类名

 

codeobject的co_code代表了字节码,这个字节码有什么含义?我们可以使用dis模块进行python的反编译:

import dis
dis.dis(co)
>>> output
 2        0 LOAD_CONST               0 (0)
          3 LOAD_CONST               1 (<code object f at 0xb7273b18, file "src_file", line 2>)
          6 MAKE_FUNCTION            1
          9 STORE_NAME               0 (f)
 5        12 LOAD_CONST              2 (9)
          15 STORE_NAME              1 (a)
 6        18 LOAD_CONST              3 (8)
          21 STORE_NAME              2 (b)
 7        24 LOAD_NAME               0 (f)
          27 CALL_FUNCTION           0
          30 POP_TOP             
          31 LOAD_CONST              4 (None)
          34 RETURN_VALUE

第一列表示以下几个指令在py文件中的行号;
第二列是该指令在指令序列co_code里的偏移量;
第三列是指令opcode的名称,分为有操作数和无操作数两种,opcode在指令序列中是一个字节的整数;
第四列是操作数oparg,在指令序列中占两个字节,基本都是co_consts或者co_names的下标;
第五列带括号的是操作数说明

3. 执行字节码

 

Python虚拟机的原理就是模拟可执行程序再X86机器上的运行,X86的运行时栈帧如下图:

 

Python虚拟机的原理就是模拟上述行为。当发生函数调用时,创建新的栈帧,对应Python的实现就是PyFrameObject对象。

假如test.py使用c实现:

const char *s = “hello”;
void func() {
    printf(“%s\n”, s);
}
int main() {
    func();
    return 0;
}

 

PyFrameObject

typedef struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;    /* 调用者的帧 */
    PyCodeObject *f_code;     /* 帧对应的字节码对象 */
    PyObject *f_builtins;     /* 内置名字空间 */
    PyObject *f_globals;      /* 全局名字空间 */
    PyObject *f_locals;       /* 本地名字空间 */
    PyObject **f_valuestack;  /* 运行时栈底 */
    PyObject **f_stacktop;    /* 运行时栈顶 */
    …….
}

 

那么对应Python的运行时栈就是这样子:

执行指令

执行test.py的字节码时,会先创建一个栈帧,以下用f表示当前栈帧,执行过程注释如下:test.py的符号名集合和常量集合

<frame object at 0x606c50>
  #返回当前stack frame
  frame     = sys._getframe(0)
  funname   = three()
  file/line = stack.py:12
<frame object at 0x180be10>
  #返回当前stack 的上层stack frame 
  frame     = sys._getframe(1)
  funname   = two()
  file/line = stack.py:7
<frame object at 0x608d30>
  frame     = sys._getframe(2)
  funname   = one()
  file/line = stack.py:4

稍作解释:sys._getframe([depth]) -> frameobject的含义,官方给出这样的解释。

Return a frame object from the call stack. If optional integer depth is
given, return the frame object that many calls below the top of the stack.
If that is deeper than the call stack, ValueError is raised. The default
for depth is zero, returning the frame at the top of the call stack.

frameobject 属性

  1. f_back -> frameobject
  2. f_builtins -> dict key:python的内置函数名
  3. f_code -> 代码对象

如果我们想更改全局变量,那么很简单,f_globals -> dict 全局对象;

对照一下上图,是不是很符合。到此算是对python运行时栈帧有了比较深一点的了解。

 

 

本文地址:http://it.zhongduwang.com/articles/python-code-operateration
版权声明:本文为原创文章,版权归 ryan007 所有,欢迎分享本文,转载请保留出处!

发表评论


表情