一 执行Python程序有两种方式
1. 交互式
优点:调试程序
缺点:无法永久保存代码
2. 命令行式
python3 py文件路径
优点:可以永久保存代码
二 实例
2.1win端
1. 交互式
C:\Users\81041>python Python 3.5.3 (v3.5.3:1880cb95a742, Jan 16 2017, 16:02:32) [MSC v.1900 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> print(\'Hello World\') Hello World
2. 命令行式
首先我们桌面创建一个myfirst.py文件,并在其中输入以下代码:
print("Hello World!")
然后再命令行中执行命名:
C:\Users\81041>python C:\Users\81041\Desktop\myfirst.py Hello World
2.2 Linux端
1. 交互式
[root@localhost-localmain ~]# python Python 3.6.2 (default, Nov 6 2017, 15:55:16) [GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> print(\'Hello World\') Hello World
2.命令行式
同样,我们在linux 下创建一个myfirst.py的文件,并输入
print("Hello World!")
然后打开终端执行命令(其实我们可以通过vim新建编辑文件内容)
[root@localhost-localmain ~]# vim myfirst.py [root@localhost-localmain ~]# python myfirst.py Hello World
在该步中,执行 python myfirst.py 时,明确的指出 myfirst.py 脚本由 python 解释器来执行。
如果想要类似于执行shell脚本一样执行python脚本,例: ./myfirst.py
,那么就需要在 myfirst.py 文件的头部指定解释器,如下:
#!/usr/bin/python print(\'Hello World\')
如此一来,执行: ./myfirst.py
即可。
注意:执行前需给予 myfirst.py 执行权限,chmod 755 myfirst.py
三 python内部执行过程
当我们利用python解释器执行py文件时,比如:python test.py,有以下几个阶段:
第一阶段:python解释器启动,此时就相当于启动了一个文本编辑器
第二阶段:python解释器相当于文本编辑器,去打开test.py,从硬盘上将test.py的文件内容读入到内存中
第三阶段:python解释器执行刚刚加载到内存中的test.py的代码(在该阶段,即执行时,才会识别python的语法,执行到字符串时,会开辟内存空间存放字符串)
python解释器与文本编辑器的异同
相同点:python解释器是解释执行文件内容的,因而python解释器具备读py文件的功能,这一点与文本编辑器一样
不同点:文本编辑器将文件内容读入内存后,是为了显示/编辑,而python解释器将文件内容读入内存后,是为了执行(识别python的语法)
3.1 执行过程概述
当我们执行加载到内存中的py代码时,Python解释器有以下几步:
第一步:对代码进行词法分析,例如:我们键入关键字或者当输入关键字有误时,都会被词法分析所触发,不正确的代码将不会被执行
第二步:对代码进行语法分析,例如:当“def func():”中,后面的冒号如果被写为其他符号,代码依旧不会被执行
第三步:将代码(.py文件)编译成字节码对象(PyCodeObject 对象),然后交由 Python 虚拟机来执行字节码指令
第四步:Python 虚拟机会从编译得到的 PyCodeObject 对象中依次读入每一条字节码指令,并在当前的上下文环境中执行这条字节码指令。我们的程序就是通过这样循环往复的过程才得以执行
图解过程如下:
3.2 PyCodeObject对象
PyCodeObject对象是Python 的解释器将代码编译成一个字节码对象。它只存在于内存中,包含了 Python 源代码中的字符串,常量值,以及通过语法解析后编译生成的字节码指令。PyCodeObject 对象还会存储这些字节码指令与原始代码行号的对应关系,这样当出现异常时,就能指明位于哪一行的代码
接下来,我们需要了解一个概念--Code Block:
Python编译器在对Python源码进行编译的时候,对代码中的一个Code Block,会创建一个PyCodeObject对象与这段代码对应。
如何确定多少代码算一个Code Block?
Python中确定Code Block的规则:当进入一个新的名字空间或作用域时,就算进入了一个新的Code Block了。
即:一个名字空间对应一个Code Block,它会对应一个PyCodeObject。
现在暂且认为名字空间就是符号的上下文环境,是名字到对象的映射。名字空间以后会详述。
在Python中,类、函数和module都对应着一个独立的名字空间,因此都会对应一个PyCodeObject对象
下面,我们新建一个py文件:test.py
s = \'test\' class A(object): pass def func(s): print(s) a = A() func(s)
Python编译器对test.py源码编译之后,会创建3个PyCodeObject对象:第一个是对应整个test.py文件代表的Code Block,第二个是对应Class A代表的Code Block,第三个是对应func代表的Code Block。
Python中有一个内置函数compile(),可以将源文件编译成codeobject,首先看这个函数的说明:
compile(source, filename, mode[, flags[, dont_inherit]]) -> code object
参数1:源文件的内容字符串
参数2:源文件名称
参数3:exec-编译module,single-编译一个声明,eval-编译一个表达式 一般使用前三个参数就够了
我们这里使用上面的test.py文件进行测试:
dir(co)可以列出co的各个域,想查看某个域直接在终端输出即可,其中co_code是指令序列,是一串二进制流。
>>> co.co_code b\'d\x00\x00Z\x00\x00Gd\x01\x00d\x02\x00\x84\x00\x00d\x02\x00e\x01\x00\x83\x03\x00Z\x02\x00d\x03\x00d\x04\x00\x84\x00\x00Z\x03\x00e\x02\x00\x83\x00\x00Z\x04\x00e\x03\x00e\x00\x00\x83\x01\x00\x01d\x05\x00S\'
指令序列co_code的格式如下:
Python内置的dis模块可以解析co_code,如下图:
说明:
第一列:表示以下几个指令在py文件中的行号
第二列:表示该指令在指令序列co_code里的偏移量
第三列:表示指令opcode的名称,分为有操作数和无操作数两种,opcode在指令序列中是一个字节的整数
第四列:表示操作数oparg,在指令序列中占两个字节,基本都是co_consts或者co_names的下标
第五列:带括号的是操作数说明
另外,从上图我们也看出函数和类也生成了PyCodeObject对象
更多关于域的了解可参考:https://www.cnblogs.com/fortwo/archive/2013/05/10/3071699.html
3.3 .pyc文件
.pyc文件是字节码在磁盘上的表现形式。一个 pyc 文件包含了三部分信息:Python 的 magic number、pyc 文件创建的时间信息,以及 PyCodeObject 对象。
magic number 是 Python 定义的一个整数值。一般来说,不同版本的 Python 实现都会定义不同的 magic number,这个值是用来保证 Python 兼容性的。比如要限制由低版本编译的 pyc 文件不能让高版本的 Python 程序来执行,只需要检查 magic number 不同就可以了。由于不同版本的 Python 定义的字节码指令可能会不同,如果不做检查,执行的时候就可能出错。
注意点:
1. 执行普通.py文件是不会产生pyc的文件的,可能因为编译器认为仅运行一次,不会被重用的,所以不会将编译生成PyCodeObject对象存储为pyc文件,Python 只会对那些以后可能继续被使用和载入的模块才会生成 pyc 文件,Python 认为使用了 import 或from…import…指令的模块,属于这种类型,才会生成 pyc 文件
2. pyc 文件在执行了 import 指令之后生成
3. 加载模块时,如果同时存在.py和.pyc,Python会尝试使用.pyc,如果.pyc的编译时间早于.py的修改时间,则重新编译.py并更新.pyc
我们如何模拟获取.pyc文件,可按如下操作:
新建一个py文件:generate_pyc.py
import imp import sys def generate_pyc(name): fp, pathname, description = imp.find_module(name) try: imp.load_module(name, fp, pathname, description) finally: if fp: fp.close() if __name__ == \'__main__\': generate_pyc(sys.argv[1])
然后执行
python generate_pyc.py test
接下来会在当前文件路径中生成一个__pycache__文件夹,里面包含一个test.cpython-35.pyc文件。如果是py2,则会直接在当前路径中生成一个.pyc文件
关于.pyc文件更多可参考:https://www.jianshu.com/p/03d81eb9ac9b
关于更多Python 程序的运行原理可参考:http://www.restran.net/2015/10/22/how-python-code-run/
关于字节码的执行可参考:http://python.jobbole.com/84599/