Python基础之第一个py程序

时间:2024-04-16 09:22:57

一  执行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 对象中依次读入每一条字节码指令,并在当前的上下文环境中执行这条字节码指令。我们的程序就是通过这样循环往复的过程才得以执行

图解过程如下:

python内部执行

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文件进行测试:

image

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的格式如下:

image

Python内置的dis模块可以解析co_code,如下图:

image

说明:

第一列:表示以下几个指令在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/