python是一门动态解释型语言。为了理解"动态"和"解释",前几天都在看《Python源码剖析》,以下是自己的一些总结。

先说解释,除开py2exe等不说(因为我不了解),python脚本需要交由python虚拟机去解释执行,当然大家都知道里面还有一个编译的过程,python虚拟机解释的只是编译后的字节码,而源代码的解释工作由python的编译模块完成。那么字节码是怎么解释执行的呢?我以为会很优雅,但实际上只是一个很长的switch-case,有1711行那么长(Python-2.7.5,参见ceval.c)。python虚拟机简单模拟了x86的堆栈模型,我当初认为她只是设置函数调用时堆栈的参数,然后通过内联汇编直接调用函数。也搞不懂当时是怎么陷入这种思维的了,可能是认为这样速度更快吧。但实际上不是的,那一段switch-case是真当自己是cpu了,对每一个指令执行固定的操作,执行的环境就是堆栈。例如:

def func(arg):
print("hello world %s" % arg) if __name__ == '__main__':
func('.')

如果说每个函数都有一个属于自己的栈帧,那么在进入print的时候,python虚拟机总共创建了3个栈帧。但可惜,这并不是c代码,而是python代码,想一下,最底层,也就是print函数内所执行的操作实现上是用c编写的,再细想,其实创建栈帧,在栈帧中传递参数也是一个个用c编写好的函数,所以这里实际上真正栈帧的数量比3要大得多。python表面上的3次函数调用实际上会转换成多个c函数调用,这就是解释型语言慢的一大原因了。

print最后是怎么执行的呢?恩,有一个字节码叫PRINT_ITEM,然后就会调用python内置的打印函数。那func又是如何调用的呢?恩,有一个字节码叫CALL_FUNCTION,专门对付非内置的python函数。必须说明的是,非内置函数分为两种:c函数和python函数,前者在python内部都被封装成PyCFunctionObject(对象类型是PyCFunction_Type),后者被封装成PyFunctionObject(对象类型是PyFunction_Type),可以想象在CALL_FUNCTION中,前者最后会调用一个c函数,而后者不过是开始新一轮的字节码执行罢了。那怎么结束呢?没关系,有表示控制流的字节码,例如RETURN_VALUE。

然后来说动态。所谓动态是什么呢?我的理解是程序运行时能知道对象的类型,也只有在运行时才能知道对象的类型。例如上面的func函数,arg是什么定义类型的,会传入什么样的参数程序(在func函数的作用域内)一无所知。但是,参数一旦传入,程序能通过参数来找到参数对应的类型,从而判断能否进行print操作。那么,对象本身必须包含对应的类型信息(以下称元信息),源代码实现是这样的:

typedef struct _typeobject {
PyObject_VAR_HEAD
const char *tp_name; /* For printing, in format "<module>.<name>" */
Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ /* Methods to implement standard operations */
...
} PyTypeObject; #define PyObject_HEAD \
_PyObject_HEAD_EXTRA \
Py_ssize_t ob_refcnt; \
struct _typeobject *ob_type; typedef struct _object {
PyObject_HEAD
} PyObject;

首先,‘对象’在python内部的表示就是PyObject结构类型(或者说所有对象的基类都是PyObject),而‘对象元信息‘的表示为PyTypeObject结构类型。可以看到PyObject内有指向PyTypeObject的指针ob_type,在对象建立之前,对象元信息必然已经存在(不然怎么知道那个对象需要多大的内在空间呢),当对象创建时,就会把元信息结构体的地址赋值给ob_type。恩,“必然已经存在”,怎么个必然法啊?内置对象当然简单,毕竟已经用c写在代码里了。但如果是用户自定义的呢?当然是从编译完的字节码里来啊。好厉害呢,编译完内存里就有这个对象的存在了。别傻了,编译完只会得到一堆字节码罢了。无论是'对象',还是'对象元信息'都是c结构体,最终这些结构体都要以传统的二进制存放在内存里。传统的c程序是放在代码段,但现在我们只能放在堆区了。所以,应该想的是,字节码的运行过程中是怎样得到对象的元信息。

这里暂停一下,自定义对象是包括函数的,别忘了一切都是对象。函数不像普通的类有基类、数据属性、函数属性等东西,所以函数对象和类对象是分开的表示。而在python的实现中,创建一个函数和创建一个普通的类是不同的操作。创建函数对应的字节码是MAKE_FUNCTION,创建的是一个PyFunctionObject对象,而它的对象元信息也是写死的PyFunction_Type,注意这是一个结构体,而不是一个结构体类型。创建的过程基本上就是把那一个函数编译得到的字节码对象(是的,字节码在内部也被封装成一个对象)的地址赋值给PyFunction_Type里的func_code罢了。好吧,创建了之后函数对象还能放在栈上吗?不行,因为栈的空间是会被复用的,所以必须把放到另外的地方。那个地方叫运行时栈上(Python源码剖析是这么叫的),存放的是map类型的信息(python叫dict类型),例如上而的func创建之后在运行时栈就会有一个映射["func" : <func obj>]。以后就可以通过"func"这个字符串来找到对应的函数对象了。

而创建一个类就不是那么简单了。例如:

class A:
name = "Class A"
def ChangeName(self, new_name):
self.name = new_name
def PrintName(self):
print("self.name: %s, class.name: %s" % (self.name, A.name))

就结果而言,当得到类A的元信息后,运行时栈会有一个映射["A" : <class_A>],其中class_A对应的类型依然是PyTypeObject。class_A中会包含一个dict(就是PyTypeObject里的tp_dict),"name", "ChangeName", "PrintName"会映射到相应的对象上。class_A也会包含用于创建实例的方法,创建实例最主要就是分配内存,那么,要分配多大的内存呢?或者说占用内存的是什么?是属于实例自身属性,也就是也以'self.'开头的变量。属于类的全局属性包含在一个dict里,和这种方法类似,属于实例的属性也包含在一个dict里面,所以无论是怎样的自定义类型,一个指针的大小就够了。当然,指针指向的空间在用到的时候也是要分配的,python使用的策略是在第一次用到的时候才真正分配,也就是类似:

if (dict == NULL)
dict = PyDict_New();
PyDict_SetItem(dict, name, value);

现在转过头去,稍微看一下class_A是怎样得到的。其实也是字节码运行的结果,所以逐行代码分析的话不如看代码或者《Python源码剖析》。大致流程是:

1. MAKE_FUNCTION,把'class A:'对应的字节码组织成一个func obj;

2. CALL_FUNCTION,执行上面的func obj,执行完后,运行时栈从栈顶开始依次是类A名字、基类列表及属性映射列表。

3. BUILD_CLASS,将上面所说的三样东西传递给build_class,将会生成一个A对应的PyTypeObject,也就是class_A,其中最关键的函数是type_new,可以想象,这里面涉及到为class_A分配内存,设置各个字段的值,例如将属性映射表放到tp_dict里之类的;

4. STORE_NAME,将"A"和class_A建立映射。

断断续续写了两三天,写得可能也不够清楚明了,如有疑问不妨自己去看一下《Python源码剖析》,毕竟我也只是挑了自己感兴趣的部分看了一下(并且对看不懂的作了自动性过滤:))。大家中秋节快乐。

python的动态与解释的更多相关文章

  1. python函数动态参数详解

    Python的动态参数: 1,参数前一个"*":在函数中会把传的参数转成一个元组. def func (*args): print(args) func(123,1,2,'a') ...

  2. python importlib动态导入模块

    一般而言,当我们需要某些功能的模块时(无论是内置模块或自定义功能的模块),可以通过import module 或者 from * import module的方式导入,这属于静态导入,很容易理解. 而 ...

  3. Python importlib(动态导入模块)

    使用 Python importlib(动态导入模块) 可以将字符串型的模块名导入 示例: import importlib module = 'module name' # 字符串型模块名 test ...

  4. 使用python type动态创建类

    使用python type动态创建类 X = type('X', (object,), dict(a=1))  # 产生一个新的类型 X 和下列方法class X(object):    a = 1效 ...

  5. Java 和 Python 解析动态 key 的 JSON 数据

    一.概述 解析JSON过程中,什么情况都可能遇到.遇到特殊的情况,不会怎么办?肯定不是设计的问题,一定是你的姿势不对. 有这样一种JSON需要解析: { "b3444533f6544&quo ...

  6. python ,__set__, __get__ 等解释

    @python __set__ __get__ 等解释 如果你和我一样,曾经对method和function以及对它们的各种访问方式包括self参数的隐含传递迷惑不解,建议你耐心的看下去.这里还提到了 ...

  7. python yield的终极解释

    (译)Python关键字yield的解释(stackoverflow): http://stackoverflow.com/questions/231767/the-python-yield-keyw ...

  8. python __name__ == ‘__main__’详细解释(27)

    学习过C语言或者Java语言的盆友应该都知道程序运行必然有主程序入口main函数,而python却不同,即便没有主程序入口,程序一样可以自上而下对代码块依次运行,然后python不少开源项目或者模块中 ...

  9. Python的动态语言特性; __slots__属性

    python是动态语言 1. 动态语言的定义 动态编程语言 是 高级程序设计语言 的一个类别,在计算机科学领域已被广泛应用.它是一类 在运行时可以改变其结构的语言 :例如新的函数.对象.甚至代码可以被 ...

随机推荐

  1. tornado做简单socket服务器(TCP)

    http://blog.csdn.net/chenggong2dm/article/details/9041181 服务器端代码如下: #! /usr/bin/env python #coding=u ...

  2. hdu 4664 Triangulation 博弈论

    看到这题时,当时还不会做,也没搞懂sg函数,于是狠狠的钻研了下博弈论,渐渐的知道了sg函数…… 现在在来做这题就很容易了,1A 打表容易发现在80左右的时候就出现循环节了 代码如下: #include ...

  3. 多线程 (四)GCD

    学习GCD要掌握几个概念 任务:需要执行的代码块可以看作一个任务 队列:把任务放到队列里,遵循先进先出的原则 队列又分为串行队列和并行队列 串行队列:顺序执行 并发队列:同时执行多个任务 同步:在当前 ...

  4. 关于DataTables一些小结

    最近项目中使用了DataTables,故小结了一下. 导入CSS文件<link rel="stylesheet" href="<%=base %>/js ...

  5. [itint5]摆放窗口

    http://www.itint5.com/oj/#47 一种做法是:把矩形所占的方格都设为-1,就是个最大子矩阵和问题.复杂度o(w^2*h)或o(w*h^2),空间W*H猜想应用场景是:电脑屏幕上 ...

  6. windows8.1下安装.NET Framework 3.5

    今天安装Arcgis10.2提示需要安装.NET Framework 3.5.校园网的网速,你懂的.所以,在线安装不太现实. 在线安装方法: 如何在 Windows 8 上安装 .NET Framew ...

  7. 不要在头文件中使用 using namespace std;

    不要在头文件中使用(using namespace std;).   若你使用了using namespace std;,在某一头文件中,那么包含这些头文件的文件就失去了"namespace ...

  8. 在Vim里使用gtags-cscope

    用Vundle安装好gtags-cscope后,要在vimrc里添加如下设置: " cscopeset cscopetag                  " 使用 cscope ...

  9. Java汉字排序(1)排序前要了解的知识(数组和list的排序接口)

    对于包含汉字的字符串来说,排序的方式主要有两种:一种是拼音,一种是笔画. 本文就讲述如何实现按拼音排序的比较器(Comparator). 作者:Jeff 发表于:2007年12月21日 11:27 最 ...

  10. maven打包源代码sources.jar和javadoc.jar帮助文档

    maven中如何打包源代码 *-sources.jar 方式一 :   命令行方式 进入cmd命令行,进入项目工程pom.xml所在路径目录,运行mvn source:jar 和 mvn javado ...