python的动态与解释
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的动态与解释的更多相关文章
- python函数动态参数详解
Python的动态参数: 1,参数前一个"*":在函数中会把传的参数转成一个元组. def func (*args): print(args) func(123,1,2,'a') ...
- python importlib动态导入模块
一般而言,当我们需要某些功能的模块时(无论是内置模块或自定义功能的模块),可以通过import module 或者 from * import module的方式导入,这属于静态导入,很容易理解. 而 ...
- Python importlib(动态导入模块)
使用 Python importlib(动态导入模块) 可以将字符串型的模块名导入 示例: import importlib module = 'module name' # 字符串型模块名 test ...
- 使用python type动态创建类
使用python type动态创建类 X = type('X', (object,), dict(a=1)) # 产生一个新的类型 X 和下列方法class X(object): a = 1效 ...
- Java 和 Python 解析动态 key 的 JSON 数据
一.概述 解析JSON过程中,什么情况都可能遇到.遇到特殊的情况,不会怎么办?肯定不是设计的问题,一定是你的姿势不对. 有这样一种JSON需要解析: { "b3444533f6544&quo ...
- python ,__set__, __get__ 等解释
@python __set__ __get__ 等解释 如果你和我一样,曾经对method和function以及对它们的各种访问方式包括self参数的隐含传递迷惑不解,建议你耐心的看下去.这里还提到了 ...
- python yield的终极解释
(译)Python关键字yield的解释(stackoverflow): http://stackoverflow.com/questions/231767/the-python-yield-keyw ...
- python __name__ == ‘__main__’详细解释(27)
学习过C语言或者Java语言的盆友应该都知道程序运行必然有主程序入口main函数,而python却不同,即便没有主程序入口,程序一样可以自上而下对代码块依次运行,然后python不少开源项目或者模块中 ...
- Python的动态语言特性; __slots__属性
python是动态语言 1. 动态语言的定义 动态编程语言 是 高级程序设计语言 的一个类别,在计算机科学领域已被广泛应用.它是一类 在运行时可以改变其结构的语言 :例如新的函数.对象.甚至代码可以被 ...
随机推荐
- Matlab求极限
matlab求极限(可用来验证度量函数或者隶属度函数)可用来验证是否收敛,取值范围等等. 一.问题来源 搜集聚类资料时,又看到了隶属度函数,没错,就是下面这个,期间作者提到m趋于2是,结果趋于1,我想 ...
- uva 108
降维 枚举行累加 然后求单行上最大连续和 #include <iostream> #include <cstring> #include <cstdio> # ...
- State Management
Samza的task可以把数据进行本地存储,并且对这些数据进行丰富的查询. 比较SQL中的select ... where...并不需要保存状态.但是aggregation和join就需要存储ro ...
- Map.entrySet() 简介
转载:http://blog.csdn.net/mageshuai/article/details/3523116 今天看Think in java 的GUI这一章的时候,里面的TextArea这个例 ...
- codeforces #313 div1 C
同BZOJ 3782 上学路线 QAQ 还比那个简单一点 把坐标(1,1)-(n,m)平移成(0,0)-(n-1,m-1) 设dp[i]表示从(1,1)出发第一次经过障碍且到达第i个障碍的方案数 首先 ...
- 封装SqlHelper
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.C ...
- Flash Builder 4.6 找不到所需的Adobe Flash Player
问题: 安装完Flash Builder 4.6 ,第一次运行项目,出现如下错误提示: “Flash Builder 找不到所需版本的 Adobe Flash Player.您可能需要安装该版本的 F ...
- Android开发之在子线程中使用Toast
在子线程中使用Toast的时候,出现Force close. 错误提示:Can't create handler inside thread that has not called Looper.pr ...
- left (outer) join , right (outer) join, full (outer) join, (inner) join, cross join 区别
z -- -- select a.*,b.* from a left join b on a.k = b.k select a ...
- poj 2109 Power of Cryptography (double 精度)
题目:http://poj.org/problem?id=2109 题意:求一个整数k,使得k满足kn=p. 思路:exp()用来计算以e为底的x次方值,即ex值,然后将结果返回.log是自然对数,就 ...