ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

python代码编译成pyd_python如何编译py文件生成pyc、pyo、pyd以及如何和C语言结合使用

2026/8/31 23:19:34 拓冰建站 浏览量
python代码编译成pyd_python如何编译py文件生成pyc、pyo、pyd以及如何和C语言结合使用 执行py文件的流程当我们去执行一个带有.py后缀这样的文件之时, 直接输入xx.py就行, 那么这个具体的流程究竟是怎样的。先给说明一下, 执行代码这件事实际上最早是把文件去打开, 然后才去执行文件里面所包含的代码, 所以说文件的扩展名并不是一定得是py这种形式, txt这种形式其实也是依旧能够成功执行的, 只要文件里面所书写的代码是符合相应规范的就行。下面我们接着来看一下具体是怎么去切实执行py文件的。先把文件当中的东西读取出来, 针对它进行扫描, 切割成一个个的token。解析token, 建立包含抽象语法树的结构, 此结构为AST, 也叫tree。对ast进行编译得到字节码code 执行字节码我们留意到了第三个过程, 它属于一个编译进程。这表明就算是解释性语言, 同样存在编译环节, 这件事跟java是一样的。之所以会有编译过程存在, 主要是为了对执行速度予以优化, 举例来说, 像元组, 或者函数里头出现了yield, 这一情况在编译阶段就已然确定了, 编译时就已晓得这是何种数据结构, 如此在执行时就能迅速地分配对应的内存。每当试图打开文件所处的目录之际, 常常会瞅见一个文件夹, 于此文件夹之内所存放的, 正是编译过后的字节码。当开始执行文件之时, 会去查验当下目录里头是否存在对应的字节码, 若不存在便予以创建, 要是存在的话, 就去比对字节码的创建时间与当前py文件的修改时间, 倘若字节码的创建时间更为靠后一些, 那就表明用户并未对文件进行修改, 进而执行字节码, 要是字节码的创建时间更早一些, 那就意味着用户修改了源代码, 如此便会重新编译进而获取一个全新的字节码。再者, 编译存在着一块至关重要的特性, 那便是语法检测。错误被划分成了两类: 其一为语法上的错误, 其二乃逻辑方面的错误。没有遵循而导致的问题在源代码当属语法错误, 就像if判断时使用了一个, 又例如for循环后面没有:等等, 诸样均是牵涉语法错误范畴, 这是一类低级别的错误, 于编译之际就会面临失败。try::pass这个代码是编译不过去的即便你使用了try···。语法错误就是不遵循规范编译的时候都编译不过。那么, 还有一种错误是逻辑错误, 这是在语法没有问题的情况下, 然而在执行之际却出现了差错, 比如说索引超出界限、与0进行除法运算、变量未曾被定义等等, 这些错误是会在运行的阶段才会显现出来的, 这是能够被捕捉到的。try::pass# 这段代码是不会报错的。如何编译py文件生成字节码存在于其中的字节码有二种, 分别是pyc以及pyo, 二者就本质而言并没有什么不同, 只是pyo在优化程度方面要更高那么一点儿。编译可以通过模块进行编译# test.py(name):print(hello name)我们来对test.py进行编译参数如下file要编译的py文件cfile, 它是编译之后的字节码文件, 要是不指定的话, 那么默认的情况是, 为源文件目录下的目录下的, 名为‘源文件名.解释器类型 - 版本.字节码类型’的文件。dfile错误消息文件默认和cfile一样一般不用管是否开启异常处理默认和False在字节码级别上进行优化, 要是属于pyc这种情况, 那么能够选择-1或者0, 换做pyo的情形下, 则能够选择1或者2, 并且都是数值越小优化的程度就越高。能够看到, 已然编译成功了, pyc是能够直接当作普通py文件进行导入的, 然而pyo好像不可以, 因而一般我们仅仅编译成pyc形式的字节码。但要是不导入只是执行的情形下, 那么是能够编译成pyo的。testtest.foo() # hello编译的另一种方式我们也可以直接使用命令行。编译成pyc-m 源代码编译成pyo-O -m 源代码如果需要编译整个目录内的所有源代码编译成pyd文件这个pyd实际上等同于上的dll文件, 然而pyd是由py文件生成的, 它能够直接作为模块被导入。至于dll, 通常是由c或者c编写的扩展模块, 这时我们会采用相应方式进行加载, 后续会提及。而pyd在linux上面呈现为so文件, dll在linux上面同样是so文件, 此时是使用特定方式还是运用普通加载模块的方式, 这要视具体情形而定。下面, 我们展开一段代码的测试, 去瞧瞧所需耗费的时长, 之后呢, 把它编译成pyd, 接着再开展一项测试。# .py():for _ in range(10000):sum 0for i in range():sum i能看到, 我们把sum逐个从0加到-1, 之后重复这种过程10000回, 我们去测试一下用了多久。timefrom funcstart time.()func()end time.()打印输出, 显示“总耗时: ”, 接着用计算得出的结束时间减去起始时间 最后是以这样的内容“总耗时: 45.”的形式体现出来。直接将py文件进行导入, 对函数进行调用并执行, 总共花费了45秒钟, 接下来我们要把它编译成pyd。那么如何编译成pyd呢先得保证在电脑之上安装了具备64位特性的MinGW, 接着去开展安装pip的操作, 随后创建一个以.py作为后缀的py文件。# .py# 导入模块.Build# 传入要编译成pyd的py文件ext .Build.(.py)# 下面还要导入另一个模块.core# 调用setup方法.core.setup(ext, # 将.Build.返回的结果传进去接着, 于命令行当中键入.py build, 进而能够将py文件.py编译成为pyd。在执行完毕以后, 会获取到一个与之对应的.c文件, 以及一个build目录。而这个所生成的c文件, 我们是无需理会的, 我们还是来瞧一瞧build目录。此时我们看到, 得到了对应的pyd文件, 它也被叫做另外的, 后面跟着的是版本号, 还有操作系统类型、位数之类的, 我们去测试一下性能吧。只将那个pyd文件拿出来, 把其他没用的都删掉。time# 我们看到导入之后显示的是pydprint() #start time.().func()end time.()打印, 双引号括起来的, 总耗时冒号, 逗号, end减去start 的结果, 双引号不包括, 总耗时冒号, 12句号换行。在这个时候, 我们带着惊奇的神情看到, 花费了12秒, 的确是提速了许多。关键在于对代码实施了优化, 除此之外, 在编译成pyd之后, 是难以再度反编译成py文件的假若是你的模块一定要开源可是又不想让他人瞧见某些细节的话, 那么便能够编译成pyd。对于字节码pyc文件的反编译已经有人达成了, 可以把pyc转变成py文件, 然而pyd当前还不曾被反编译过。那么请问, 为何在编译成为pyd之际, 速度会出现提升的情况? 主要原因在于, 对代码实施了优化操作, 使其转化成为了c一级别的代码。另外, 我们来讲, .cp38 - .pyd之中的38乃是解释器的版本, 而我们这里为.8。如此这般, 也就意味着, 唯有当你的版本为.8的时候, 才会去导入此模块, 那么进而, 我们将中间那一串予以删掉, 仅保留.pyd, 这种做法可不可以? 我们能够进行尝试。print() #实际状况表明确实能够做到, 除此之外如此这般之下不光.8版本这样, 其他版本同样是能够实施导入操作的, 只要对于将代码编译成pyd时所运用的py文件, 满足能够进行执行诠释使用的诠释器的语法标准便可。结合c语言我们讲运用, 诚然能够令代码加速, 然而必定仍然比不上原生的c语言执行得那般快。我们把上面的代码转变为c的代码去做测试, 借此引入怎样把和c进行融合。//1.c(){int _;long long sum;long i;for (_ 0;_ 10000; _ )sum 0;for (i 0;i ; i)sum i;sum;接着, 我们把这个1.c搞成dll, 在linux里那就是so, 借助命令gcc -o, 那编译过后的dll或者so文件名, 再加上 - c源文件编译。在我们这个地方, 那就把它编译成mmp.dll吧, 所以具体的操作是, 使用gcc, 使其生成mmp.dll, 并且针对1.c进行编译。可以看到mmp.dll已经出现了 下面就来调用它time# 调用.cdll.传入dll的路径这个方法等同于采用.CDLL(xxx.dll)这样的形式, 两种采用方式中的哪一种都可以, 不过存在要求, 要求dll或者so的路径必须是绝对路径。另外, 这两种方式, 在上面加载, 于dll方面是可行的, 在linux上面加载, 当涉及到so时, 同样也都是可以达成相应情况的。.dll文件等于, 通过.cdll方式, 引用位于“C:\Users\uu\mmp.dll”这个路径下的文件。start time.()此刻只需将dll视作一个模块就行, 其中定义了诸多函数, 像func。dll.func()end time.()打印‘总耗时: ’, 结束减去开始 , 这里总耗时为2。能够看到, 用时不足3秒, 然而我运用原生的去执行, 却需要45秒, 就算使用加速, 也仍需12秒。首先, 我得明确指出, 当sum逐个从0加到 -1的时候, long long是存不下的。可是, 具有相同功能的程序, c的速度势必会比编译后的pyd快, 这一点, 自己能够去测试一番, 我这边就不再去试了。类型和c语言类型显然, 我们无条件地直接去调用一个函数是不存在任何难题的, 然而, 要是其中某个函数是需要带有参数的呢? 那么我们进行直接传递的时候, 还能够传递原生类型吗, 是这样的吗句号。//计算两个数之和(inta,longb){int sum;sum a b;sum;//查找指定字符在字符串中出现的位置(char*,){char *p;int pos 0;对于, p进行特定操作, 当, p所指向, 的值, 不等于空字符时, 进行该特定操作, 并, p自增, 同时, pos自增。if (*p )pos;-1;dll .cdll.(rC:\Users\\\\mmp.dll)打印, 动态链接库, 添加, 一百, 与, 二百, 的结果, 此结果为, 三百。print(dll.(, a)) # -1就整型而言, 我们瞧见不存在问题, 然而针对于字符串, 却出现了问题, 鉴于在c里面, 并不存在字符串的观念, 在这样的情况下, 那该采取怎样的做法呢?from ,dll .cdll.(rC:\Users\\\\mmp.dll)在C语言里, 不存在字符串这一响当当的概念, 于C语言当中的字符串, 事实上是字符方面的数组, C拥有的此类种种概念, 就不再展开予以介绍了传递一个指针, 该指针指向字符数组。如同字符a一样, 不能直接传递, 而是需要进行封装, 并且在其中要去传递字节。将dll.((b), (ba))进行打印操作, # 1。那于是我们来瞧一瞧究竟给我们给予了哪一些类别, 而这些类别又分别对应着c之中的哪一些类别呀?from *print(c_int(1)) # (1)print((1)) # (1)print((1)) # (1)print((1)) # (1)print((1)) # (1)print((1)) # (1)print((1)) # (1)print((1)) # (1)print((1.1)) # (1.1858)print((1.1)) # (1.1)# 在64位机器上等于print((1.1)) # (1.1)print((True)) # (True)必须传递一个字节, 或者传递只有一个元素的字符数组, 或者传递一个int。# 代表c里面的字符展现出打印双引号包裹的表示字节的字符a, 这种形式表示的字节单个字符a, 还要展现出双引号包裹的表示字节的字符x, 这种形式表示的字节单个字符x, 会出现什么情况。# 传递一个字符print((憨)) # (憨)# 和类似但是要求传递一个整型print((97)) # (97)print((97)) # (97)# 就是c里面字符数组指针了# char *s hello world;那么, 这里面, 要传递、一个字符数组, 字符, 是bytes类型, 返回一个地址。该语句, 可将参数(bhello world)进行打印操作, 其打印后所对应的结果为(24)。# 直接传递一个同样返回一个地址print((憨八嘎~)) # ()# 并且还有一个和# 相当于和这个和机器有关print((10)) # (10)print((10)) # (10)自然地, c里头的各类形式, 在各个方面都是存在相符对应的那种特性, 就好像我们未曾予以阐述的结构体之类的, 更具繁杂程度的运用方式能够去参照官方网站。