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. Android中SQLite应用详解(转)

    上次我向大家介绍了SQLite的基本信息和使用过程,相信朋友们对SQLite已经有所了解了,那今天呢,我就和大家分享一下在Android中如何使用SQLite. 现在的主流移动设备像Android.i ...

  2. spoj 390

    简单题  记得uva上有个一样的  画个图就好了 #include <cstdio> #include <cmath> const double pi = acos(-1); ...

  3. "Principles of Reactive Programming" 之<Actors are Distributed> (2)

    Actor Path 我们知道actor是有层级的(hierarchical),第.每个actor在它的父actor的名字空间下都有一个名字.这样就构成了一个树状的结构,就像是文件系统.每个actor ...

  4. python time相关操作

    1.获取当前时间的两种方法: 代码如下: import datetime,timenow = time.strftime("%Y-%m-%d %H:%M:%S")print now ...

  5. Coder-Strike 2014 - Round 1(A~E)

    题目链接 A. Poster time limit per test:1 secondmemory limit per test:256 megabytesinput:standard inputou ...

  6. WebLoigc的配置(生产模式与开发模式)

    1.Weblogic两种模式的切换1).生产模式--->开发模式将domain路径下的bin\setDomainEnv.cmd文件中set PRODUCTION_MODE=true改为set P ...

  7. UVA548——Tree(中后序建树+DFS)

    Tree You are to determine the value of the leaf node in a given binary tree that is the terminal nod ...

  8. How to Send an HTTP Header With Every Request With Spring RestTemplate

    In Know Which Apps Are Hitting Your Web Service, I showed how to write a servlet filter that enforce ...

  9. nginx + tomcat

    http://blog.csdn.net/sun305355024sun/article/details/8620996

  10. The Impact of Garbage Collection on Application Performance

    As we’ve seen, the performance of the garbage collector is not determined by the number of dead obje ...