Python .py生成.pyd文件並打包.exe 的註意事項說明

最近用python寫瞭一個小程序,想發佈出去讓人試用又不想暴露源碼,搜索瞭一下發現將py文件編譯成pyd文件就能達到目的。

轉換過程很簡單,但是在調用pyd文件並且打包為單個exe文件的時候遇到一個坑,搞瞭一天才解決,在這裡分享一下。

首先安裝cython庫

個人比較喜歡用清華的鏡像庫,速度快。

pip install Cyphton -i https://pypi.tuna.tsinghua.edu.cn/simple

然後創建一個setup.py文件

寫入以下內容:

from distutils.core import setup
from Cython.Build import cythonize
setup(ext_modules=cythonize("BetaV14.py"))

BetaV14.py就是要轉換為pyd文件的代碼文件

命令行輸入:

python setup.py build_ext --inplace

會在.py文件目錄下生成一個BetaV14.cp37-win_amd64.pyd文件,文件名中“.cp37-win_amd64”這一段可以刪除,不刪除也可以正常調用;但原文件名字段不能改變。

接下來需要打包發佈為.exe文件

我用的是pyinstaller,還是用清華鏡像庫安裝。

pip install pyinstaller -i https://pypi.tuna.tsinghua.edu.cn/simple

根據一些教程,有的說在命令行直接輸入:

pyinstaller -F BetaV14.py

就能直接引用pyd文件打包發佈exe文件,但是在我這裡出現文件缺失的錯誤:

ValueError: Module file F:\python項目1\BetaV14.py is missing

繼續查找問題,發現需要用一個入口程序來導入pyd文件,於是創建一個main.py文件,import剛才生成的模塊,pyd文件默認優先級高於py文件,可以在後面解包exe文件來驗證。

import BetaV14
if __name__ == '__main__':
 BetaV14()

這裡需要註意的是程序的__main__入口隻能有一個,如果源py文件中有定義main入口,需要註釋掉並調整代碼縮進,否則通過main.py調用pyd文件遇到if name == ‘main’:之後的代碼都不會運行。

接著命令行輸入:

pyinstaller -F main.py

打包成.exe文件,在dist目錄下發現main.exe文件大小隻有5M,之前采用py文件打包的程序有接近50M,運行之後閃退。這個問題想瞭半天才想出來,可能是引用瞭大量的第三方庫沒有打包進去,於是將源py文件頭部import部分全部復制到main.py文件頭部。

import win32gui
import win32api
import win32con
import time 
import random
import datetime
import os,sys
import configparser
import numpy as np
from PIL import Image
from scipy.signal import convolve2d
import http.client
import subprocess
import BetaV14
if __name__ == '__main__':
 BetaV14()

再次用命令pyinstaller -F main.py打包,得到正常大小的.exe文件,點擊能正常運行。

接下來我們用pyinstxtractor.py(不清楚該腳本是否涉及著作權,請自行搜索)解包exe文件驗證一下,命令行輸入:

python pyinstxtractor.py main.exe

會得到一個main.exe_extracted文件夾,在文件夾下發現文件BetaV14.pyd,說明通過引用pyd文件打包成功。

在此作為一個初學者記錄一下自己遇到的坑,讓大佬們見笑瞭。

補充:python打包編譯成pyd或者_python之setup.py的那些事

今天偶然對setup.py產生瞭興趣,以前隻知道可以用它來安裝包,例如

python setup.py build ->python setup.py install.當然前提你下載的這個源碼包是壓縮的,之前對這個理解並不深,今天偶然看見pip install -e . 的用法,然後串起來想瞭一下。

我的目錄結構如上,首先我創建瞭一個setuptutorial的directory,然後我在下面創建瞭greet_pkg的python package,並且在setuptutorial下面創建瞭setup.py如下

from setuptools import setup, find_packages
 
setup(
 name='greet',
 version='1.0.0',
 packages=find_packages(include=['greet_pkg', 'greet_pkg.*']),
 url='',
 license='uestc',
 author='jack',
 author_email='[email protected]',
 description='test package',
 py_modules=['greet2'],
 install_requires=['pyjokes']
)

greet2.py如下

def greet2(name):
 print(
 'hello',name,'this is greet2'
 )

在greet_pkg下面下瞭一個greet.py如下

import pyjokes
def greet(name):
 print('hello!', name, f'im telling you a joke {pyjokes.get_joke()}')

整體目錄結構和setup.py就如上所示

接下來好戲開場瞭,如果我要在任意其他文件裡面使用到我定義的greet()方法,以前的做法是按照import規則在其他文件裡面導入,當然如果寫的不規范,及其的容易出問題,這裡我提供另外一個思路,在setuptutorial下面使用pip install -e . 命令,將setup.py裡面包含的package和py_module安裝到Libary root下,當然他的實際的location不是在Libary root下,這個你可以在pip install -e . 之後使用pip show greet 查看他的信息.

到這裡就完瞭嗎?

當然沒有,這個就是之前的python setup.py build 的作用,我這裡猜測大概率是把tar.gz包轉化成我上述的目錄結構一樣的directory。

而python setup.py install 的作用就類似於pip install ‘-e’ . 。而且python setup.py install 之後的greet包是真的存在於sitepackages裡面的。

setup.py除瞭上述安裝包的作用,還可以是他的逆過程如 python setup.py sdist 成greet.tar.gz,這樣就有上述的裝包的過程先build再install。

還可以使用setup.py將py文件轉化為pyd,也可以說將pyx文件轉化為pyd,

from setuptools import setup
# from distutils.core import setup
from Cython.Build import cythonize
 
# setup(
# name='hello',
# ext_modules=cythonize(['sayhi.py'])
# )

然後運行python setup.py build_ext –inplace就可以瞭!

pyd文件可以很好的隱蔽py文件裡面的實現,和linux下的so文件類似。

有類似py->pyd功能的有easycython模塊,可以直接pip安裝。

有人可能會說pyc也看不見源碼嗎?

但是他可以被反編譯23333

至於如何將py編譯成pyc或pyo

可以使用py_compile或者compileall,不瞭解的可以自行搜索一下,都有很多的例子,還有針對pyc的反編譯庫,都可以搜到,至此setup.py我所瞭解的功能都談完瞭,裡面還有很多參數可以靈活配置,實現更加炫酷的效果可以查看這個鏈接setup.py

推薦閱讀: