python如何编译py文件生成pyc、pyo、pyd以及如何和C语言结合使用
python执行py文件的流程
当我们执行一个py文件的时候,直接python xx.py即可,那么这个流程是怎么样的呢。先说明一下,python执行代码实际上是先打开文件然后执行里面的代码,所以文件的扩展名不一定是py的形式,txt形式也是依旧可以成功执行,只要文件里面的代码是符合python规范的。下面我们来看看python是怎么执行py文件的。
先将文件里面的内容读取出来,scanner对其进行扫描,切分成一个个的tokenparser对token进行解析,建立抽象语法树(AST,abstract syntax tree)compiler对ast进行编译,得到python字节码code evaluator执行字节码
我们注意到第三个过程,是一个编译的过程。说明python即便是解释性语言,也依旧存在着编译的过程,这一点和java是一样的。之所以要存在编译的过程,主要是为了优化执行的速度,比如元组,或者函数里面出现了yield,这一点在编译的时候就已经确定了,编译的时候就已经知道这是一个什么样的数据结构,那么在执行的时候可以很快速的分配相应的内存。我们在打开python文件所在的目录的时候,总会看到一个__pycache__的文件夹,这里面存放的就是python编译之后的字节码。当执行python文件的时候,会检测当前的__pycache__目录中是否有对应的字节码,没有就创建,有的话比较字节码的创建时间和当前py文件的修改时间,如果字节码的创建时间要晚一些,说明用户没有修改文件,于是执行字节码,如果字节码的创建时间要早一些,说明用户修改了python源代码,那么就会从新编译得到一个新的字节码。此外编译还有一个重要的特点,就是语法检测。错误分为两种:一种是语法错误,另一种是逻辑错误。
语法错误就是源代码没有遵循python的规范,比如if判断使用了一个=,或者for循环后面没有:等等,这些都是属于语法错误,这是一种低级的错误,在编译的时候就会失败。try:
>
except Exception:
pass
"""
这个代码是编译不过去的,即便你使用了try···except。
语法错误就是不遵循python规范,编译的时候都编译不过。
"""
那么另一种错误就是逻辑错误,这是语法没问题,但是执行的时候出错了,比如索引越界、和0相除、变量没有定义等等,这些错误是在运行的时候才会出现的,这是可以被捕获的。try:
a
except Exception:
pass
# 这段代码是不会报错的。
python如何编译py文件生成字节码
python中的字节码有两种,pyc和pyo,两者本质上没啥区别,只不过pyo的优化程度更高一些。
编译可以通过py_compile模块进行编译
# test.py
def foo(name):
print("hello " + name)
我们来对test.py进行编译
import py_compile
"""
参数如下:
file:要编译的py文件
cfile:编译之后的字节码文件,不指定的话默认为源文件目录下的__pycache__目录的下的'源文件名.解释器类型-python版本.字节码类型'文件
dfile:错误消息文件,默认和cfile一样,一般不用管
doraise:是否开启异常处理,默认和False
optimize:优化字节码级别。如果是pyc:可以选-1或0。pyo的话,可以选1或2。都是值越小优化程度越高
"""
py_compile.compile(file="test.py", cfile=r"./test.pyc", optimize=-1)
py_compile.compile(file="test.py", cfile=r"./test.pyo", optimize=1)

可以看到,已经编译成功了,pyc是可以直接当做普通py文件导入的,但是pyo貌似不可以,所以一般我们只编译成pyc形式的字节码。但是如果不导入只是执行的话,那么是可以编译成pyo的。
import test
test.foo("mashiro") # hello mashiro
编译的另一种方式,我们也可以直接使用命令行。
编译成pyc
python -m py_compile 源代码
编译成pyo
python -O -m py_compile 源代码
如果需要编译整个目录内的所有源代码
python compileall
编译成pyd文件
这个pyd实际上就是Windows上的dll文件,但是pyd是由py文件生成的,是可以直接当成python模块导入的。而dll的话一般是c或者c++编写的扩展模块,这个时候我们会使用ctypes进行加载,后面会说。而Windows的pyd在linux上面则是so文件,dll在linux上面也是so文件,这个时候是使用ctypes还是使用普通加载模块的方式,就看具体情况了。
我们下面测试一段python代码,看看会用多长时间,然后将其编译成pyd之后再测试一下。
# test_v.py
def func():
for _ in range(10000):
sum = 0
for i in range(100000):
sum += i
可以看到我们将sum依次从0加到100000-1,然后重复这个过程10000次,我们来测试一下用了多长时间。
import time
from test_v import func
start = time.perf_counter()
func()
end = time.perf_counter()
print("总耗时:", end - start) # 总耗时: 45.554086
直接导入py文件,调用函数执行,总共花了45秒钟,下面我们来编译成pyd。
那么如何编译成pyd呢?
首先确保电脑上安装了64位的MinGW,然后安装cython,pip install cython,新建一个py文件to_pyd.py。
# to_pyd.py
# 导入模块
import Cython.Build
# 传入要编译成pyd的py文件
ext = Cython.Build.cythonize("test_v.py")
# 下面还要导入另一个模块
import distutils.core
# 调用setup方法
distutils.core.setup(
ext_modules=ext, # 将Cython.Build.cythonize返回的结果传进去
)
然后在命令行输入python to_pyd.py build,即可把py文件test_v.py编译成pyd。执行之后,会得到一个对应的test_v.c文件,以及一个build目录。这个生成的c文件我们不需要管,我们看看build目录。

我们看到此时就得到了对应的pyd文件,也叫test_v,后面的则是python的版本号以及操作系统类型、位数等等,我们来测试一下性能吧。只把那个pyd文件拿出来,其他没用的都删掉,
import time
import test_v
# 我们看到导入之后,显示的是pyd
print(test_v) # <module 'test_v' from 'C:\\Users\\satori\\Desktop\\love_minami\\test_v.cp38-win_amd64.pyd'>
start = time.perf_counter()
test_v.func()
end = time.perf_counter()
print("总耗时:", end - start) # 总耗时: 12.3021872
此时我们惊奇地看到,用了12秒,确实快了不少。主要是cython将python代码进行了优化,另外编译成pyd之后,是很难再反编译成py文件的,如果你的模块必须开源但是又不想被人看到某些细节的话,那么就可以编译成pyd。对于字节码pyc文件的反编译已经有人实现了,可以将pyc转成py文件,但是pyd目前还没有被反编译过。
那为什么编译成pyd的时候速度会提升呢?主要是cython将代码进行了优化,转化成了c一级的代码。另外我们说,test_v.cp38-win_amd64.pyd里面的38就是解释器的版本,我们这里是python3.8。这样的话,也就意味着只有当你的版本是python3.8的时候,才会去导入这个模块,于是我们把中间那一串给删掉只保留test_v.pyd可不可以呢?我们可以试一下
import test_v
print(test_v) # <module 'test_v' from 'C:\\Users\\satori\\Desktop\\love_minami\\test_v.pyd'>
事实证明确实是可以的,另外这样的话不光是python3.8,其他版本的python也是可以导入的,只要编译成pyd所使用的py文件,符合执行的python解释器的语法规范即可。
python结合c语言
我们说使用cython确实能够加速代码,但肯定还是没有原生的c语言执行的快。我们将上面的代码转换成c的代码来测试一下,进而引入如何将python和c进行结合。
//1.c
long long func()
{
int _;
long long sum;
long i;
for (_ = 0;_ < 10000; _ ++)
{
sum = 0;
for (i = 0;i < 100000; i++)
{
sum += i;
}
}
return sum;
}
然后我们将这个1.c编译成dll,在linux中就是so,通过命令gcc -o 编译之后的dll或者so文件名 -shared c源文件编译。
我们这里就编译成mmp.dll吧:所以是gcc -o mmp.dll -shared 1.c

可以看到mmp.dll已经出现了, 下面就来调用它
import time
import ctypes
# 调用ctypes.cdll.LoadLibrary,传入dll的路径
# 这个方法就等价于dll = ctypes.CDLL("xxx.dll"),用哪种都行,但是要求dll或者so的路径是绝对路径
# 另外这两种方式在Windows上加载dll和linux上加载so都是可以的。
dll = ctypes.cdll.LoadLibrary(r"C:\Users\satori\Desktop\love_minami\mmp.dll")
start = time.perf_counter()
# 此时把dll看成一个模块即可,里面定义了很多函数,比如func
dll.func()
end = time.perf_counter()
print("总耗时:", end - start) # 总耗时: 2.3377831
可以看到用时不到3秒,而我使用原生的python执行需要45秒,使用cython加速也需要12秒。首先我必须指出,当sum依次从0加到100000-1时,long long存不下。但是相同功能的程序,c的速度肯定会比cython编译的pyd快,这一点可以自己测试一下,我这里就不再试了。
ctypes类型和c语言类型
我们直接调用一个函数显然是没有问题的,但如果函数里面需要参数呢?我们还能直接传递python的原生类型吗?
//计算两个数之和
int add (int a, long b)
{
int sum;
sum = a + b;
return sum;
}
//查找指定字符在字符串中出现的位置
int find_pos(char *string, char subchar)
{
char *p;
int pos = 0;
for (p = string; *p != '\0'; p++, pos++){
if (*p == subchar)
{
return pos;
}
}
return -1;
}
import ctypes
dll = ctypes.cdll.LoadLibrary(r"C:\Users\satori\Desktop\love_minami\mmp.dll")
print(dll.add(100, 200)) # 300
print(dll.find_pos("satori", "a")) # -1
我们看到对于整型来说是没有问题的,但是对于字符串就有问题了,因为c中没有字符串的概念,这时候应该怎么做呢?
import ctypes
from ctypes import c_char_p, c_char
dll = ctypes.cdll.LoadLibrary(r"C:\Users\satori\Desktop\love_minami\mmp.dll")
# c语言中没有字符串这个概念,c语言中的字符串实际上是字符数组,c的这些概念不再介绍
# 传递一个指向字符数组的指针,同理字符a不能直接传递,需要使用c_char包装一下,并且里面需要传递字节。
print(dll.find_pos(c_char_p(b"satori"), c_char(b"a"))) # 1
所以我们来看看ctypes给我们提供了哪些类型,这些类型又对应c中的哪些类型呢?

from ctypes import *
print(c_int(1)) # c_long(1)
print(c_uint(1)) # c_ulong(1)
print(c_short(1)) # c_short(1)
print(c_ushort(1)) # c_ushort(1)
print(c_long(1)) # c_long(1)
print(c_ulong(1)) # c_ulong(1)
print(c_longlong(1)) # c_longlong(1)
print(c_ulonglong(1)) # c_ulonglong(1)
print(c_float(1.1)) # c_float(1.100000023841858)
print(c_double(1.1)) # c_double(1.1)
# 在64位机器上,c_longdouble等于c_double
print(c_longdouble(1.1)) # c_double(1.1)
print(c_bool(True)) # c_bool(True)
# 必须传递一个字节或者只有一个元素的字符数组,或者一个int
# 代表c里面的字符
print(c_char(b"a"), c_char(bytearray(b"x"))) # c_char(b'a') c_char(b'x')
# 传递一个unicode字符
print(c_wchar("憨")) # c_wchar('憨')
# 和c_char类似,但是要求传递一个整型
print(c_byte(97)) # c_byte(97)
print(c_ubyte(97)) # c_ubyte(97)
# c_char_p就是c里面字符数组指针了
# char *s = "hello world";
# 那么这里面也要传递一个字符数组,字符是bytes类型,返回一个地址
print(c_char_p(b"hello world")) # c_char_p(2082736374464)
# 直接传递一个unicode,同样返回一个地址
print(c_wchar_p("憨八嘎~")) # c_wchar_p(2884583039392)
# 并且还有一个c_size_t和c_ssize_t
# 相当于c_ulonglong和c_longlong,这个和机器有关
print(c_size_t(10)) # c_ulonglong(10)
print(c_ssize_t(10)) # c_longlong(10)
当然c中各种类型,在ctypes都有对应。比如我们没有介绍的结构体等等,更复杂的用法可以参考官网。
python如何编译py文件生成pyc、pyo、pyd以及如何和C语言结合使用的更多相关文章
- py文件生成pyc
鼠标右键 在此处打开命令行 python -m compileall xxx.py可以对当前目录下的xxx.py文件生成pyc
- 如何生成pyc/pyo/pyd文件
# 一.如何生成pyc/pyo文件 # 1.通过编写代码生成 import py_compile # 参数如下 ''' def compile(file, cfile=None, dfile=None ...
- 代码编译与反编译 (.py文件与.pyc文件互转)
# 将.py文件转化为.pyc文件,实现代码隐藏的需要,转化后的.pyc文件将在当前目录的__pycache__文件夹下. # .pyc文件的使用与.py文件的使用相同. .py -> .pyc ...
- python下编译py成pyc和pyo
python下编译py成pyc和pyo 其实很简单, 用 python -m py_compile file.py python -m py_compile /root/src/{file1,f ...
- python之模块py_compile用法(将py文件转换为pyc文件)
# -*- coding: cp936 -*- #python 27 #xiaodeng #python之模块py_compile用法(将py文件转换为pyc文件):二进制文件,是由py文件经过编译后 ...
- 转载:【学习之家】Python中__init__.py文件的作用
Python中__init__.py文件的作用详解 Python中__init__.py文件的作用详解 来源:学习之家 作者:xuexi110 人气:357 发布时间:2016-09-29 摘要:__ ...
- python进阶之py文件内置属性
前言 对于任何一个python文件来说,当python解释器运行一个py文件,会自动将一些内容加载到内置的属性中:一个模块我们可以看做是一个比类更大的对象. 查看模块的内置属性 我们先创建一个典型的p ...
- Python工程编译成跨平台可执行文件(.pyc)
原文:https://blog.csdn.net/zylove2010/article/details/79593655 在某些场景下,若不方便将python编写的源码工程直接给到其他人员,则可以将p ...
- 第三百七十六节,Django+Xadmin打造上线标准的在线教育平台—创建用户操作app,在models.py文件生成5张表,用户咨询表、课程评论表、用户收藏表、用户消息表、用户学习表
第三百七十六节,Django+Xadmin打造上线标准的在线教育平台—创建用户操作app,在models.py文件生成5张表,用户咨询表.课程评论表.用户收藏表.用户消息表.用户学习表 创建名称为ap ...
随机推荐
- 转载:深入浅出 Java 8 Lambda 表达式
原文地址:http://viralpatel.net/blogs/Lambda-expressions-java-tutorial/ OneAPM for Java 能够深入到所有 Java 应用内部 ...
- What's binary search?
Binary Search: Search a sorted array by repeatedly dividing the search interval in half. Begin with ...
- liunx 中如何删除export设置的环境变量
1,网上有资料说,export命令添加的环境变量,利用export -p 删除: 例如:export KUBECONFIG="/etc/kubernetes/admin.conf" ...
- APP安全_Android渗透环境
Android渗透 移动APP大多通过WEB API服务的方式与服务端进行交互,这种模式把移动安全和web安全绑在一起.常见的web漏洞在移动APP中也存在,比如SQL注入,文件上传,中间件/serv ...
- 解析Health端点数据获取异常数据
问题: 由于健康节点响应类型是Health类,并且由于需要执行的健康状态类不固定,返回的类型数据的字段不是固定的,所以使用了@JsonAngGetter 注解,照成了解析这个JSON 结构非常不方便. ...
- Kafka性能调优分析-线上环境篇
一.背景介绍: 在平时的开发中,使用kafka来发送数据已经非常熟悉,但是在使用的过程中,其实并没有比较深入的探索kafka使用过程中 一些参数配置,带来的损失可能就是没有充分的发挥出kfka的优势, ...
- Elasticsearch5.x 引擎健康情况
查看引擎健康情况 [root@w]# curl -XGET "http://localhost:9200/_cat/health?v" epoch timestamp cluste ...
- SQLite基础-4.数据定义语言(DDL)
目录 一.创建数据库 1. 创建方式 2. 数据库命名规范 二. 创建表 1. 基本用法 2. 数据表命名规范 3. 字段命名规范 三. 删除表 一.创建数据库 1. 创建方式 在第二章中我们讲了如何 ...
- Java基础题记录
1. 装箱和拆箱 装箱:自动将基本数据类型转换为包装器类型即引用数据类型 拆箱:将包装器类型转换为基本数据类型 2. Java的8中基本数据类型 关键字 字节数 范围 默认值 boolelan 1by ...
- k8s基础知识
k8s部署到云计算上的优势 传统模式: 在k8s外部部署一个外置调度器,请求先到物理node节点ip地址,由物理服务地址再转发代理至service地址,如果node节点也是私网地址呢,就需要再外部部署 ...