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. 团体程序设计天梯赛-练习集L1-007. 念数字

    L1-007. 念数字 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 翁恺 输入一个整数,输出每个数字对应的拼音.当整数为负数时,先 ...

  2. hdu 1851 A Simple Game 博弈论

    简单博弈问题(巴什博弈-Bash Game) 巴什博弈:只有一堆n个物品,两个人轮流从这对物品中取物,规定每次至少取一个,最多取m个,最后取光着得胜. 很容易想到当n%(m+1)!=0时,先取者必胜, ...

  3. mybatis整合redis

    mybatis默认缓存是PerpetualCache,可以查看一下它的源码,发现其是Cache接口的实现:那么我们的缓存只要实现该接口即可. 编写Redis需要用的2个工具类   RedisUtil. ...

  4. memcached部署memcached环境及PHP扩展

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://lxsym.blog.51cto.com/1364623/876209 Memca ...

  5. 垃圾收集器GC的种类

    堆内存的结构:

  6. Ubuntu 学习笔记

    1.   ubuntu开启root账号,设置分配很简单,只要为root设置一个root密码就行了: $ sudo passwd root 之后会提示要输入root用户的密码,连续输入root密码,再使 ...

  7. SRM 587 DIV1

    要掉到DVI2了..好不容这次的250那么简单,500的题知道怎么做,可惜没调出来500. 250的题很简单,从第1步到第N步,每次要么不做,要么走i步,且X不能走,问说最远走多远. #include ...

  8. 重载操作符 operator overloading 学习笔记

    重载操作符,只是另外一种调用函数的方法和表现方式,在某些情况它可以让代码更简单易读.注意不要过度使用重载操作符,除非它让你的类更简单,让你的代码更易读. 1语法 如下: 其中友元,关键字不是必须的,但 ...

  9. XMPP聊天客户端环境搭建

    1.服务器选择:ejabberd,具体安装过程请参考:http://blog.csdn.net/linhanmin/article/details/9876819 2.客户端配置: 采用xmppfra ...

  10. 【HDOJ】4358 Boring counting

    基本思路是将树形结构转线性结构,因为查询的是从任意结点到叶子结点的路径.从而将每个查询转换成区间,表示从该结点到叶子结点的路径.离线做,按照右边界升序排序.利用树状数组区间修改.树状数组表示有K个数据 ...