1. 解释性语言和编译性语言
- 1.1 定义
- 1.2 Python 属于编译型还是解释型?
- 1.3 收获
2. 动态类型语言
- 2.1 定义
- 2.2 比较
2. 动态语言(动态编程语言)
- 3.1 定义
- 3.2 Python 动态语言的体现
- 3.3 __slots__()
1. 解释性语言和编译性语言
1.1 定义
计算机是不能够识别高级语言的,所以当我们运行一个高级语言程序的时候,就需要一个“翻译机”来从事把高级语言转变成计算机能读懂的机器语言的过程。这个过程分成两类,一类是编译,一类是解释。
解释型语言
程序执行前不需要先进行编译,而是在执行时才通过解释器对代码进行翻译,翻译一句然后执行一句,直至结束。
编译型语言
编译性语言写的程序在被执行之前,需要一个专门的编译过程,把程序编译成为机器语言(二进制代码)的文件,比如 exe 文件,此后再运行时就不用重新翻译了,直接使用编译后的结果文件(exe 文件)来运行就行。
因为编译型语言在程序运行之前就已经对程序做出了“翻译”,所以在运行时就少掉了“翻译”的过程,因此效率比较高。但是我们也不能一概而论,一些解释型语言也可以通过解释器的优化来在对程序做出翻译时对整个程序做出优化,从而在效率上超过编译型语言。
此外,随着 Java 等基于虚拟机的语言的兴起,我们又不能把语言纯粹地分成解释型和编译型这两种。用 Java 来举例,Java 首先是通过编译器编译成字节码文件(不是二进制码),然后在运行时通过解释器(JVM)给解释成机器代码才能在各个平台执行,这同时也是 Java 跨平台的原因。所以我们说 Java 是一种先编译后解释的语言。
总结:将由高级语言编写的程序文件转换为可执行文件(二进制的)有两种方式,编译和解释,编译是在程序运行前,已经将程序全部转换成二进制码,而解释是在程序执行的时候,边翻译边执行。
- 编译型语言:执行效率高;依靠编译器,因此跨平台性差些。
- 解释型语言:执行效率低;依靠解释器,因此跨平台性好。
1.2 Python 属于编译型还是解释型?
其实 Python 和 Java 一样,也是一门基于虚拟机的语言,我们先来从表面上简单地了解一下 Python 程序的运行过程。
当我们在命令行中输入 python hello.py 时,其实是激活了 Python 的“解释器”,告诉“解释器”要开始工作了。可是在“解释”之前,其实执行的第一项工作和 Java 一样,是编译。
熟悉 Java 的同学可以想一下我们在命令行中如何执行一个 Java 的程序:
- javac hello.java(编译的过程)
- java hello(解释的过程
只是我们在用 Eclipse 等 IDE 时,将这两步给融合成了一步而已。其实 Python 也一样,当我们执行 python hello.py 时,他也一样执行了这么一个过程,所以我们应该这样来描述 Python,Python 是一门先编译后解释的语言。
1.2.1 简述 Python 的运行过程
在说这个问题之前,我们先来说两个概念,PyCodeObject 和 pyc 文件。
当 Python 程序首次运行时,编译的结果保存在位于内存中的 PyCodeObject 中。当 Python 程序运行结束时,Python 解释器则将 PyCodeObject 写回到硬盘中的 pyc 文件中。
当 Python 程序第二次运行时,首先程序会在硬盘中寻找 pyc 文件,如果找到则直接载入,否则就重复上面的过程。
所以我们应该这样来定位 PyCodeObject 和 pyc 文件,我们说 pyc 文件其实是 PyCodeObject 的一种持久化保存方式。
也就是说保存 pyc 文件是为了下次再次使用该脚本时避免重复编译,以此来节省时间。因此,只执行一次的脚本,就没必要保存其编译结果 pyc 文件,这样只是浪费空间。下面举例解释。
示例 1:执行不含“import”关键字的代码
a.py 代码如下:
print("hello world")
执行 a.py:
E:\test>python a.py
hello world
此时我们可以发现,在执行所在目录下并没有产生 pyc 文件,仍只有 a.py。
示例 2:执行含“import”关键字的代码
新增 b.py,代码如下:
import a
执行 b.py:
E:\test>python b.py
hello world
此时我们可以发现,pyc 文件产生了。
1.2.2 pyc 文件的目的是重用
编译型语言的优点在于,我们可以在程序运行时不用解释,而直接利用已经“翻译”过的文件。也就是说,我们之所以要把 py 文件编译成 pyc 文件,最大的优点在于我们在运行程序时,不需要重新对该模块进行重新的解释。
所以,我们需要编译成 pyc 文件的应该是那些可以重用的模块,这与我们在设计软件时是一样的目的。所以 Python 解释器认为:只有 import 进来的模块,才是需要被重用的模块。
这个时候也许有人会说,不对啊!你的这个问题没有被解释通啊,我的 test.py 不是也需要运行么,虽然不是一个模块,但是以后我每次运行也可以节省时间啊!OK,我们从实际情况出发,思考下我们在什么时候才可能运行 python xxx.py:
- 执行测试。
- 开启一个 web 进程。
- 执行一个程序脚本。
第一种情况(执行测试),这时哪怕所有的文件都没有 pyc 文件都是无所谓的。
第二种情况(开启一个 web 进程),我们试想一个 web 程序通常这样执行:
或
然后这个程序就类似于一个守护进程一样一直监听着 8181/9002 端口,而一旦中断,只可能是程序被杀死,或者其他的意外情况,那么你需要恢复要做的是把整个的 Web 服务重启。既然一直监听着,把 PyCodeObject 一直放在内存中就足够了,完全没必要持久化到硬盘上。
最后一种情况(执行一个程序脚本),一个程序的主入口其实很类似于 Web 程序中的 Controller,也就是说,它负责的应该是 Model 之间的调度,而不包含任何的主逻辑在内,如在 http://www.cnblogs.com/kym/archive/2010/07/19/1780407.html 中所提到,Controller 应该就是一个 Facade(外观模式),无任何的细节逻辑,只是把参数转来转去而已。做算法的同学可以知道,在一段算法脚本中,最容易改变的就是算法的各个参数,那么这个时候给持久化成 pyc 文件就未免有些画蛇添足了。
所以我们可以这样理解 Python 解释器的意图,Python 解释器只把我们可能重用到的模块持久化成 pyc 文件。
1.2.3 pyc 的过期时间
说完了 pyc 文件,可能有人会想到,每次 Python 的解释器都把模块给持久化成了 pyc 文件,那么当我的模块发生了改变的时候,是不是都要手动地把以前的 pyc 文件 remove 掉呢?
当然 Python 的设计者是不会犯这么白痴的错误的。而这个过程其实就取决于 PyCodeObject 是如何写入 pyc 文件中的。
我们来看一下 import 过程的源码吧:
这段代码比较长,我们只看标注的代码,其实它在写入 pyc 文件的时候,写了一个 Long 型变量,变量的内容则是文件的最近修改日期,同理,我们再看下加载 pyc 的代码:
不用仔细看代码,我们可以很清楚地看到原理,其实每次在加载 pyc 之前都会先检查一下 py 文件和 pyc 文件保存的最后修改日期,如果不一致则重新生成一份 pyc 文件。
1.3 收获
其实了解 Python 程序的执行过程对于大部分程序员(包括 Python 程序员)来说意义都是不大的,那么真正有意义的是,我们可以从 Python 的解释器的做法上学到什么,我认为有这样的几点:
- 其实 Python 是否保存成 pyc 文件和我们在设计缓存系统时是一样的,我们可以仔细想想,到底什么是值得扔在缓存里的,什么是不值得扔在缓存里的。
- 在跑一个耗时的 Python 脚本时,我们如何能够稍微压榨一些程序的运行时间,就是将模块从主模块分开(虽然往往这都不是瓶颈)。
- 在设计一个软件系统时,重用和非重用的东西是不是也应该分开来对待,这是软件设计原则的重要部分。
- 在设计缓存系统(或者其他系统)时,我们如何来避免程序的过期,其实 Python 的解释器也为我们提供了一个特别常见而且有效的解决方案。
2. 动态类型语言
2.1 定义
动态类型语言
所谓动态类型语言,就是(变量、属性、方法以及方法的返回值)类型的检查(确定)是在运行时才做。
即编译时与类型无关。一般在变量使用之前不需要声明变量类型,而变量的类型通常是由被赋的值的类型决定。 如 Php、Python 和 Ruby。
静态类型语言
与动态类型语言正好相反,在编译时便需要确定类型的语言。即写程序时需要明确声明变量类型。如 C/C++、Java、C# 等。
对于动态语言与静态语言的区分,套用一句流行的话就是:Static typing when possible, dynamic typing when needed。
强类型语言
强制数据类型定义的语言。也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了。因此强类型定义语言是类型安全的语言。
弱类型语言
数据类型可以被忽略的语言。它与强类型定义语言相反, 一个变量可以赋不同数据类型的值。
强类型定义语言在速度上可能略逊色于弱类型定义语言,但是强类型定义语言带来的严谨性能够有效的避免许多错误。
Python 属于动态类型语言和弱类型语言。
2.2 比较
严格意义上,强类型与静态类型不是一回事,同理弱类型和动态类型。
- 强类型是指某门语言检查两种类型是否兼容,如果不兼容就抛出一个错误或强制类型转换,尽管这个说法并不是很严格。
- 静态类型强迫在类型结构的基础上执行多态。判断是否是一只鸭子的依据,是其基因蓝图(静态)还是因其叫声和走路的姿态像一只鸭子(动态)。
静态类型语言:
- 优点:在于其结构非常规范,突出显示代码以便于调试,方便类型安全。
- 缺点:需要写更多的类型相关代码(如声明变量),不便阅读(特别是当你看别人代码时,会连变量定义也看吗?想必不会,看结构,看方法的含义想必才是本质)。
动态类型语言:
- 优点:在于方便阅读,不需要写非常多的类型相关代码。
- 缺点:自然是不便调试,命名不规范时会造成读不懂,不利于理解等。
在强类型、静态类型语言的支持者,与动态类型、自由形式的支持者之间,经常发生争执:
- 前者主张,在编译的时候就可以较早发现错误,而且还可增进运行时期的性能。
- 后者主张,使用更加动态的类型系統,分析代码更为简单,减少出错机会,才能更加轻松快速的编写程序。
3. 动态语言(动态编程语言)
3.1 定义
根据维基百科,动态(编程)语言的定义如下:
3.2 python动态语言的体现
动态语言是一门在运行时可以改变其结构的语言,这句话如何理解?
示例 1:运行过程中给(实例)对象添加属性
class Person(object): def __init__(self, name=None, age=None): self.name = name self.age = age Jack = Person("Jack",18) print(Jack.age)
在上述代码中,我们定义了 Person 类,然后创建了 Jack 对象,打印对象的 age 属性,这没毛病。现实中人除了名字和年龄,还会有其他属性,例如身高和体重。我们尝试打印一下身高属性。
print(Jack.height)
毫无疑问,这会报错,因为 Person 类中没有定义 height 属性。
但是如果在程序运行的时候添加 height 属性,会发生什么呢?
Jack.height = 170 print(Jack.height) # 输出结果:170 setattr(Jack, 'height', 170) print(Jack.height) # 输出结果:170
在上述代码中,我们给 Jack 添加了 height 属性,然后打印,没有报错,可以输出结果。
我们再打印一下对象的属性:
print(Jack.__dict__) # 输出结果:{'name': 'Jack', 'age': 18, 'height': 170}
本来对象是没有 height 属性,但是可以在程序运行过程中给实例对象动态绑定属性,这就是动态语言的魅力。
需要注意:
Mia = Person('Mia', 18) print(Mia.__dict__) # 输出结果:{'name': 'mia', 'age': 18}
Mia 对象居然没有 height 属性。为什么?事实上我们只是给类示例动态地绑定了一个属性,而不是给类绑定属性,所以重新创建的对象是没有 height 属性的。如果想要给类添加,也是可以的。
示例 2:动态给类添加属性
Person.height = None Mia = Person("Mia", 18) print(Mia.height) # 输出结果:None
示例 3:动态给对象添加方法
class Person(object): def __init__(self,name=None,age=None): self.name = name self.age = age def speak_name(self): print(self.name)
Jack = Person("Jack", 18) Jack.speak_name = speak_name Jack.speak_name(Jack) # Jack print(Jack.__dict__) # {'name': 'Jack', 'age': 18, 'speak_name': <function speak_name at 0x000001F86CAE1E18>} Mia = Person("Mia", 18) print(Mia.__dict__) # {'name': 'Mia', 'age': 18}
在上述代码中,对象 Jack 的属性中已经成功添加了 speak_name 函数。但是!有没有感觉 Jack.speak_name(Jack) 这个语句很别扭。按习惯来说,应该 Jack.speak_name() 就行了。如果想要达到这种效果,应该要像下面这样子做:
import types Jack.speak_name = types.MethodType(speak_name, Jack) Jack.speak_name() # 输出结果:Jack
其中 MethodType 用于绑定方法对象。
示例 4:动态给类添加方法
import types class Person(object): def __init__(self, name=None, age=None): self.name = name self.age = age def speak_ok(cls): print(OK) Person.speak_name = types.MethodType(speak_ok, Person) Person.speak_ok() # OK
示例 5:动态删除属性/方法
Mia = Person("Mia", 18) delattr(Mia,'height') # 等价于 del Mia.height print(Mia.__dict__) # 输出结果:{'name': 'mia', 'age': 18}
总结
- 给实例对象添加属性/方法:对象名.属性/方法名 = xxxx
- 给类对象添加属性/方法:类名.属性/方法名 = xxxx
- 给实例/类对象删除属性/方法:
- del 实例/类对象.属性/方法名
- delattr(实例/类对象, "属性/方法名")
3.3 __slots__()
通过以上例子可以得出一个结论:相对于动态语言,静态语言具有严谨性!所以,玩动态语言的时候,小心动态的坑!
如果我们想要限制实例对象的属性怎么办?比如,只允许对 Person 的实例对象添加 name 和 age 属性。
为了达到限制的目的,Python 允许在定义类的时候,定义一个 __slots__() 方法,来限制该实例对象能添加的属性:
>>> class Person: ... __slots__ = ("age", "name") ... >>> p = Person() >>> p.age = 12 >>> p.name = "xiaoming" >>> p.hobby = "football" Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Person' object has no attribute 'hobby'
注意:__slots__ 定义的属性仅对当前类的实例对象起作用,对继承的子类是不起作用的:
>>> class Student(Person): ... pass ... >>> s = Student() >>> s.hobby = "football" >>>