*.py PyCodeObject PyFrameObject

Python是由3个主要部分组成,运行语句的解释器、将源代码编译成中间字节码的编译器和运行.pyc文件的python虚拟机PVM组成。

当python脚本运行时,在代码开始运行处理之前,python会执行一些步骤,第一是编译成所谓的字节码,第二是将其转发到所谓的虚拟机中。

       程序执行时,pyhton通过把每一条源语句分解为单一步骤来将这些源语句翻译成一组字节码指令,编译只是一个简单的翻译步骤,这些字节码指令是源代码底层的与平台无关的表现形式。如果python进程在机器上有写入权限,那么它将把程序的字节码保存为一个.pyc为扩展名的文件中(经过编译的.py源代码)。Python这样做的目的是作为一种启动速度的优化,下一次运行程序的时候,如果在上次保存字节码之后没有修改过源代码的话,python将会加载.pyc文件并跳过编译这个步骤。Python会自动检查源文件和字节码文件的时间戳,如果又重新保存了源代码,则字节码将自动重新创建。

       一旦程序被编译成字节码,之后字节码被发送到称为PVM的python虚拟机上来执行。PVM是python的运行引擎,它时常表现为python系统的一部分,是实际运行脚本的组件。实际上,PVM就是迭代运行字节码指令的一个大循环,一个接一个地完成操作。

Python 的虚拟机是Python 的核心,在.py 源代码被编译器编译为字节码指令序列之后,就将由Python 的虚拟机接手整个工作。Python 的虚拟机会从编译得到的PyCodeObject对象(Python代码的编译结果就是PyCodeObject对象)中依次读入每一条字节码指令,并在当前的上下文环境中执行这条字节码指令。如此反复运行,所有由Python 源代码所规定的动作都会如期望一样,一一展开。

字节码在Python虚拟机程序里对应的是PyCodeObject对象。.pyc文件是字节码在磁盘上的表现形式。Python 源代码经过编译之后,所有的字节码指令以及程序的其他静态信息都存放在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;

PyCodeObject对象的创建时机是模块加载的时候。

*.py文件在作为模块导入的时候,运行的时候会被编译成.pyc文件。如下执行mytest.py文件会对mytest.py进行编译成字节码并解释执行,但是不会生成mytest.pyc。但如果mytest.py加载了其他模块,如import otherfile,Python会对otherfile.py进行编译成字节码,生成otherfile.pyc,然后对字节码解释执行。

[root@localhost newtest]# ll

total 28

-rw-r--r--  1 root root  20 May 31 11:39 a.py

-rw-r--r--  1 root root 446 May 31 11:39 mytest.py                    #该文件中导入a.py

[root@localhost newtest]# pythontest.py

[root@localhost newtest]# ll

total 32

-rw-r--r--  1 root root  20 May 31 11:39 a.py

-rw-r--r--  1 root root 121 May 31 11:40 a.pyc

-rw-r--r--  1 root root 446 May 31 11:39 test.py

[root@localhost newtest]#

如果想生成mytest.pyc,我们可以使用Python内置模块py_compile来编译(Python代码的编译结果就是PyCodeObject对象)。加载模块时,如果同时存在.py和.pyc,Python会尝试使用.pyc,如果.pyc的编译时间早于.py的修改时间,则重新编译.py并更新.pyc。如

[root@localhostnewtest]# python -m py_compile mytest.py           # -m py_compile相当于import py_compile

[root@localhostnewtest]# ll

total32

-rw-r--r--  1 root root  20 May 31 11:39 a.py

-rw-r--r--  1 root root 121 May 31 11:54 a.pyc

-rw-r--r--  1 root root 446 May 31 11:39 mytest.py

-rw-r--r--  1 root root 587 May 31 11:56 mytest.pyc

[root@localhost newtest]# pythonmytest.pyc

in module A

[root@localhostnewtest]#

编译源代码有以下作用:

1、源代码保护(算法保护)/ 防止用户篡改源代码

2、解释器加载代码速度加快

如果用python3执行该脚本,还会在当前目录下生成一个__pycache__文件夹。

To speed up loading modules, Python caches the

compiled version of each module in the __pycache__ directory under

the name module.version.pyc, where the version encodes the format of the

compiled file; it generally contains the Python version number. For example, in

CPython release 3.7the compiled version of spam.py would be cached

as __pycache__/spam.cpython-37.pyc.

[root@localhost newtest]# cd__pycache__

[root@localhost __pycache__]# ll

total 4

-rw-r--r-- 1 root root 125 May 3112:55 a.cpython-37.pyc

python程序的运行并不单单靠PyCodeObject对象维护的静态环境,也需要一些关于程序运行的其他动态信息执行环境。如下一段python代码

#[environment.py]

i ='Python'

deff():

i = 999

print i          #1

f()

print i              #2

在上面代码中的1 和2 两个地方,都进行了同样的动作,即print i。显然,它们所对应的字节码指令肯定是相同的,但是这两条语句的执行效果是不同的。这样的结果正是在执行环境的影响下产生的。在执行“1”处的print 时,执行环境中,i 的值为999;而在执行2 处的print 时,执行环境中i 的值为“Python”。像这种同样的符号在程序运行的不同时刻对应不同的值,甚至不同类型的情况,必须在运行时动态地被捕捉和维护。这些信息是不可能在PyCodeObject 对象中被静态地存储的。

    我们都知道C语言使用栈帧保存函数中的变量信息,我们可以用这样的机理来定性地解释environment.py 的执行过程。Python 的虚拟机实际上是在模拟操作系统运行可执行文件的过程,当Python 开始执行environment.py 中的第一条表达式时,Python 已经建立起了一个执行环境A,所有的字节码指令都会在这个执行环境中执行。Python 可以从这个执行环境中获取变量的值,也可以根据字节码指令的指示修改执行环境中某个变量的值,以影响后续的字节码指令。这样的过程会一直持续下去,直到发生了函数的调用行为。

当Python 在执行环境A 中执行调用函数f 的字节码指令时,会在当前的执行环境A之外重新创建一个新的执行环境B,在这个新的执行环境B 中,有一个新的名字为“i”的对象。这个新的执行环境B 实际上是一个新的栈帧。而Python 正是在其虚拟机中通过不同的实现方式模拟了这一原理,从而完成了Python 字节码指令序列的执行。

当发生函数调用时,创建新的栈帧,对应Python的实现就是PyFrameObject对象。Python 源码中对PyFrame-Object的定义:

typedef struct _frame {

PyObject_VAR_HEAD

struct _frame *f_back; //执行环境链上的前一个frame

PyCodeObject *f_code;

//PyCodeObject 对象

PyObject *f_builtins; //builtin 名字空间

PyObject *f_globals; //global 名字空间

PyObject *f_locals; //local 名字空间

PyObject **f_valuestack; //运行时栈的栈底位置

PyObject **f_stacktop; //运行时栈的栈顶位置

……

int f_lasti; //上一条字节码指令在f_code 中的偏移位置

int f_lineno; //当前字节码对应的源代码行

……

//动态内存,维护(局部变量+cell 对象集合+free

对象集合+运行时栈)所需要的空间

PyObject *f_localsplus[1];

} PyFrameObject;

从f_back 我们可以看出一点,在Python 实际的执行中,会产生很多PyFrameObject对象,而这些对象会被链接起来,形成一条执行环境链表。这正是对x86 机器上栈帧间关系的模拟。

在f_code 中存放的是一个待执行的PyCodeObject对象,而接下来的f_builtins、f_globals、f_locals 是3 个独立的名字空间,在这里我们看到了名字空间和执行环境之间的关系。名字空间实际上是维护着变量名和变量值之间关系的PyDictObject对象,所以,在这3 个PyDictObject 中,分别维护了builtin 的name、global 的name,以及local 的name 与对应值之间的映射关系。

尽管PyFrameObject对象是一个用于Python虚拟机实现的极为隐秘的内部对象,但是Python还是提供了某种途径可以访问到PyFrameObject对象。在Python中,有一种frame

object,它是对C一级的PyFrameObject的包装。非常幸运的是,Python提供的一个方法能方便地获得当前处于活动状态的frame

object。这个方法就是sys模块中的_getframe方法。

下面的caller.py演示了如何利用获得当前活动的frame

object,进而获取调用当前函数的函数的信息:

importsys

value= 3

defg():

         frame = sys._getframe()

         print 'current function is : ',frame.f_code.co_name

         caller = frame.f_back

         print 'caller function is : ',caller.f_code.co_name

         print "caller's local namespace :", caller.f_locals

         print "caller's global namespace :",

         print caller.f_globals.keys()

deff():

        

         a = 1

         b = 2

         g()

defshow():

         f()

>>>show()

currentfunction is :  g

callerfunction is :  f

caller'slocal namespace :  {'a': 1, 'b': 2}

caller'sglobal namespace :  ['g', 'f','__builtins__', 'show', 'value', 'sys', '__name__', '__doc__']

>>> 

从执行的结果可以看到,从函数f 中我们完全获得了其调用者——函数g 的一切信息,甚至包括函数g 的各个名字空间。

所以说在Python 真正执行的时候,它的虚拟机实际上面对的并不是一个PyCodeObject对象,而是另一个对象——PyFrameObject。它就是我们所说的执行环境。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容

  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 5,744评论 0 10
  • 1. 简单的例子 先从一个简单的例子说起,包含了两个文件 foo.py 和 demo.py 执行这个程序pytho...
    jiangmo阅读 1,699评论 0 5
  • 模块和包 一 模块 1 什么是模块? 常见的场景:一个模块就是一个包含了python定义和声明的文件,文件名就是...
    go以恒阅读 2,271评论 0 4
  • C++调用python 在C/C++中嵌入Python,可以使用Python提供的强大功能,通过嵌入Python可...
    Bruce_Szh阅读 13,783评论 1 7
  • 1. 这几天放假,实习要从下周一开始,大四了根本就没有课。 所以每天的日常就是在玩手机中度过,不是躺着就是坐着玩。...
    晴风秋末燕归来阅读 455评论 0 1