淺析python如何實現(xiàn)類似C++一樣調(diào)用動態(tài)庫
多種實現(xiàn)方案,核心思路是將可變的核心業(yè)務邏輯抽離到動態(tài)庫/可動態(tài)加載的模塊中,主程序EXE僅保留固定的加載和調(diào)用邏輯,后續(xù)更新只需替換動態(tài)庫,無需重新打包EXE。
下面詳細介紹幾種可行方案,從簡單到進階,兼顧易用性和實用性:
一、方案1:最簡易——Python動態(tài)加載.py模塊(無需編譯,快速落地)
這是最輕量化的方案,無需處理編譯相關的復雜操作,核心是利用Python的importlib標準庫,實現(xiàn)運行時動態(tài)加載/重新加載.py模塊,主程序打包為EXE后,只需替換.py模塊文件即可更新邏輯。
核心原理
- 主程序(打包為EXE):僅負責動態(tài)加載業(yè)務模塊、提供調(diào)用入口,邏輯固定不變;
- 業(yè)務模塊(單獨存放的
.py文件):存放可變的核心邏輯(如你的手機拍照測試數(shù)據(jù)處理、上傳邏輯); - 后續(xù)更新:直接修改/替換
.py業(yè)務模塊,無需重新打包EXE,主程序下次運行(或熱重載)即可加載新邏輯。
實現(xiàn)步驟
步驟1:拆分主程序和業(yè)務模塊
業(yè)務模塊:business_logic.py(可變,后續(xù)可更新,不打包進EXE)
# 核心業(yè)務邏輯(示例:手機測試結(jié)果處理)
def handle_test_result(phone_sn, photo_path):
"""處理手機拍照測試結(jié)果(可變邏輯,后續(xù)可優(yōu)化更新)"""
print(f"正在處理SN號:{phone_sn} 的測試照片:{photo_path}")
# 此處可放置實際的上傳、數(shù)據(jù)校驗等邏輯
result = {
"code": 0,
"msg": "處理成功",
"phone_sn": phone_sn,
"photo_path": photo_path,
"handle_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
return result
def get_test_version():
"""返回業(yè)務模塊版本(便于確認是否更新成功)"""
return "v1.0.0"
主程序:main.py(固定邏輯,后續(xù)打包為EXE)
import importlib
import os
import sys
from datetime import datetime
def load_business_module(module_name="business_logic"):
"""
動態(tài)加載業(yè)務模塊
:param module_name: 業(yè)務模塊名稱(無需.py后綴)
:return: 加載成功的模塊對象
"""
# 確保業(yè)務模塊在可搜索路徑中(優(yōu)先當前EXE運行目錄)
exe_dir = os.path.dirname(sys.executable) if hasattr(sys, 'frozen') else os.getcwd()
sys.path.append(exe_dir)
try:
# 動態(tài)導入模塊(支持重新加載,避免緩存)
if module_name in sys.modules:
return importlib.reload(sys.modules[module_name])
else:
return importlib.import_module(module_name)
except ModuleNotFoundError:
raise Exception(f"未找到業(yè)務模塊 {module_name}.py,請將其放置在EXE同目錄下")
except Exception as e:
raise Exception(f"加載業(yè)務模塊失敗:{str(e)}")
if __name__ == "__main__":
# 固定邏輯:加載業(yè)務模塊 + 調(diào)用核心方法
try:
# 1. 動態(tài)加載業(yè)務模塊
business_mod = load_business_module()
# 2. 調(diào)用業(yè)務模塊的方法(固定調(diào)用接口,業(yè)務模塊內(nèi)部可更新)
print(f"當前業(yè)務模塊版本:{business_mod.get_test_version()}")
test_result = business_mod.handle_test_result("SN123456789", "./test_photo.jpg")
print(f"測試結(jié)果處理完成:{test_result}")
except Exception as e:
print(f"程序運行失敗:{str(e)}")
input("按回車鍵退出...")
步驟2:打包主程序為EXE
使用pyinstaller打包main.py,注意排除業(yè)務模塊business_logic.py:
# 安裝pyinstaller pip install pyinstaller # 打包命令(--onefile打包為單個EXE,--exclude-module排除業(yè)務模塊) pyinstaller -F main.py --exclude-module business_logic
步驟3:運行與更新
- 打包完成后,將
business_logic.py復制到EXE所在目錄; - 運行EXE,即可正常調(diào)用業(yè)務模塊邏輯;
- 后續(xù)更新:直接修改
business_logic.py(如優(yōu)化handle_test_result邏輯、更新版本號),替換EXE同目錄下的該文件,重新運行EXE即可加載新邏輯,無需重新打包。
方案優(yōu)勢與局限
- 優(yōu)勢:零編譯、操作簡單、學習成本低,適合快速迭代的業(yè)務邏輯;
- 局限:
.py文件源碼暴露,安全性較低;僅支持Python語法,無法調(diào)用C/C++等底層邏輯。
二、方案2:進階——調(diào)用Python編譯的.pyd動態(tài)庫(隱藏源碼,性能提升)
.pyd文件是Python的動態(tài)鏈接庫(Windows平臺,對應Linux的.so文件),本質(zhì)是將Python代碼編譯為二進制動態(tài)庫,既可以隱藏源碼,又能實現(xiàn)「更新無需重打包EXE」的效果,核心邏輯與方案1一致,只是將.py模塊替換為.pyd動態(tài)庫。
核心原理
- 將可變業(yè)務邏輯的
.py文件編譯為.pyd動態(tài)庫; - 主程序(EXE)通過
importlib動態(tài)加載.pyd文件,調(diào)用方式與.py模塊完全一致; - 后續(xù)更新:只需編譯新的
.pyd文件,替換EXE同目錄下的舊文件即可,無需重打包EXE。
實現(xiàn)步驟
步驟1:編譯.py業(yè)務模塊為.pyd
使用cython將business_logic.py編譯為.pyd(隱藏源碼+提升運行性能):
安裝依賴:
pip install cython
創(chuàng)建編譯腳本setup.py:
from setuptools import setup
from Cython.Build import cythonize
import os
# 要編譯的Python文件
py_file = "business_logic.py"
setup(
name="PhoneTestBusinessLogic",
ext_modules=cythonize(
py_file,
language_level=3, # 對應Python3版本
annotate=False # 是否生成注釋文件,默認False
),
zip_safe=False
)
執(zhí)行編譯命令:
python setup.py build_ext --inplace
編譯完成后,會在當前目錄生成business_logic.cp3x-win_amd64.pyd(cp3x對應你的Python版本,如cp39對應Python3.9),可將其重命名為business_logic.pyd(方便調(diào)用)。
步驟2:主程序加載.pyd動態(tài)庫
主程序main.py無需修改(與方案1完全一致),importlib會自動識別.pyd文件并加載,調(diào)用方式與.py模塊無差異。
步驟3:打包與更新
- 打包
main.py為EXE(排除business_logic相關文件); - 將
business_logic.pyd復制到EXE同目錄下,運行EXE即可正常調(diào)用; - 后續(xù)更新:修改
business_logic.py后,重新編譯為.pyd,替換舊文件即可,無需重打包EXE。
方案優(yōu)勢與局限
- 優(yōu)勢:源碼隱藏、運行性能比純
.py高、調(diào)用方式兼容,兼顧安全性和易用性; - 局限:需要掌握
cython編譯技巧,跨平臺兼容性稍差(Windows為.pyd,Linux為.so)。
三、方案3:終極——調(diào)用C/C++編譯的動態(tài)庫(.dll/.so,高性能+跨語言)
這是與C++調(diào)用動態(tài)庫完全一致的方案,Python通過ctypes(標準庫)或cffi(第三方庫)調(diào)用C/C++編譯的.dll(Windows)/.so(Linux)動態(tài)庫,核心業(yè)務邏輯用C/C++實現(xiàn)并編譯為動態(tài)庫,主程序(Python EXE)僅負責調(diào)用動態(tài)庫接口,后續(xù)更新只需替換.dll/.so文件,無需重打包EXE。
核心原理
- C/C++編寫核心業(yè)務邏輯,暴露標準C接口(避免C++名稱修飾問題);
- 將C/C++代碼編譯為
.dll/.so動態(tài)庫; - Python主程序通過
ctypes加載動態(tài)庫,調(diào)用暴露的接口; - 后續(xù)更新:修改C/C++代碼,重新編譯為動態(tài)庫,替換舊文件即可,無需重打包Python EXE。
實現(xiàn)步驟(以Windows平臺.dll為例)
步驟1:C/C++編寫并編譯.dll動態(tài)庫
編寫C代碼:business_core.c(核心業(yè)務邏輯,暴露C接口)
#include <stdio.h>
#include <time.h>
#include <string.h>
// 定義返回結(jié)果的結(jié)構(gòu)體(與Python端對應)
typedef struct {
int code;
char msg[128];
char phone_sn[64];
char photo_path[256];
char handle_time[32];
} TestResult;
// 暴露C接口(extern "C" 避免C++名稱修飾,C代碼可省略)
#ifdef _WIN32
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT
#endif
// 獲取當前時間字符串
static void get_current_time(char *time_buf, int buf_size) {
time_t now = time(NULL);
struct tm *t = localtime(&now);
strftime(time_buf, buf_size, "%Y-%m-%d %H:%M:%S", t);
}
// 核心業(yè)務接口:處理手機測試結(jié)果
DLL_EXPORT void handle_test_result(const char *phone_sn, const char *photo_path, TestResult *result) {
// 初始化結(jié)果結(jié)構(gòu)體
memset(result, 0, sizeof(TestResult));
// 填充結(jié)果數(shù)據(jù)
result->code = 0;
strcpy(result->msg, "處理成功");
strcpy(result->phone_sn, phone_sn);
strcpy(result->photo_path, photo_path);
get_current_time(result->handle_time, sizeof(result->handle_time));
printf("正在處理SN號:%s 的測試照片:%s\n", phone_sn, photo_path);
}
// 核心業(yè)務接口:獲取版本號
DLL_EXPORT const char *get_test_version() {
return "v1.0.0 (C/C++ DLL)";
}
編譯為.dll(使用MinGW或MSVC,以MinGW為例):
# MinGW編譯命令:生成business_core.dll gcc -shared -o business_core.dll business_core.c -Wall
編譯完成后,得到business_core.dll動態(tài)庫。
步驟2:Python主程序調(diào)用.dll動態(tài)庫
主程序main.py(固定邏輯,打包為EXE):
import ctypes
import os
import sys
# 定義與C結(jié)構(gòu)體對應的Python類(通過ctypes映射)
class TestResult(ctypes.Structure):
_fields_ = [
("code", ctypes.c_int),
("msg", ctypes.c_char * 128),
("phone_sn", ctypes.c_char * 64),
("photo_path", ctypes.c_char * 256),
("handle_time", ctypes.c_char * 32)
]
def load_c_dll(dll_name="business_core.dll"):
"""動態(tài)加載C/C++ DLL動態(tài)庫"""
# 確保DLL在EXE運行目錄
exe_dir = os.path.dirname(sys.executable) if hasattr(sys, 'frozen') else os.getcwd()
dll_path = os.path.join(exe_dir, dll_name)
if not os.path.exists(dll_path):
raise Exception(f"未找到動態(tài)庫 {dll_path},請將其放置在EXE同目錄下")
try:
# 加載DLL
dll = ctypes.WinDLL(dll_path)
return dll
except Exception as e:
raise Exception(f"加載動態(tài)庫失?。簕str(e)}")
if __name__ == "__main__":
try:
# 1. 加載C/C++ DLL動態(tài)庫
dll = load_c_dll()
# 2. 配置DLL接口的參數(shù)和返回值類型
# 配置get_test_version接口
dll.get_test_version.restype = ctypes.c_char_p
# 配置handle_test_result接口
dll.handle_test_result.argtypes = [
ctypes.c_char_p, # phone_sn(C字符串)
ctypes.c_char_p, # photo_path(C字符串)
ctypes.POINTER(TestResult) # 結(jié)果結(jié)構(gòu)體指針
]
dll.handle_test_result.restype = None
# 3. 調(diào)用DLL接口
# 調(diào)用獲取版本號
version = dll.get_test_version().decode("utf-8")
print(f"當前動態(tài)庫版本:{version}")
# 調(diào)用處理測試結(jié)果接口
test_result = TestResult()
phone_sn = b"SN123456789"
photo_path = b"./test_photo.jpg"
dll.handle_test_result(phone_sn, photo_path, ctypes.byref(test_result))
# 解析結(jié)果并打印
print("\n測試結(jié)果處理完成:")
print(f" 狀態(tài)碼:{test_result.code}")
print(f" 提示信息:{test_result.msg.decode('utf-8')}")
print(f" 手機SN號:{test_result.phone_sn.decode('utf-8')}")
print(f" 照片路徑:{test_result.photo_path.decode('utf-8')}")
print(f" 處理時間:{test_result.handle_time.decode('utf-8')}")
except Exception as e:
print(f"程序運行失?。簕str(e)}")
input("按回車鍵退出...")
步驟3:打包與更新
用pyinstaller打包main.py為EXE:
pyinstaller -F main.py
將business_core.dll復制到EXE同目錄下,運行EXE即可正常調(diào)用C/C++動態(tài)庫邏輯;
后續(xù)更新:修改business_core.c代碼,重新編譯為business_core.dll,替換舊文件即可,無需重新打包Python EXE。
方案優(yōu)勢與局限
- 優(yōu)勢:高性能(C/C++編譯執(zhí)行)、源碼完全隱藏、支持跨語言調(diào)用(與C++項目無縫銜接)、更新靈活;
- 局限:需要掌握C/C++開發(fā)和動態(tài)庫編譯技巧,調(diào)試難度較高,跨平臺需分別編譯
.dll和.so。
四、核心總結(jié)與方案選型建議
三種方案核心對比
| 方案 | 動態(tài)庫類型 | 優(yōu)點 | 缺點 | 適用場景 |
|---|---|---|---|---|
| 方案1 | .py模塊 | 零編譯、易上手、快速迭代 | 源碼暴露、性能一般 | 內(nèi)部測試、快速迭代、無源碼保密需求 |
| 方案2 | .pyd動態(tài)庫 | 源碼隱藏、性能提升、調(diào)用兼容 | 需Cython編譯、跨平臺稍差 | 有源碼保密需求、核心邏輯為Python編寫 |
| 方案3 | .dll/.so動態(tài)庫 | 高性能、源碼安全、跨語言 | 需C/C++開發(fā)、編譯復雜 | 高性能需求、與C++項目對接、嚴格源碼保密 |
核心要點(實現(xiàn)「無需更新EXE」的關鍵)
- 主程序(EXE)僅保留固定的加載/調(diào)用邏輯,不包含可變的核心業(yè)務邏輯;
- 可變邏輯抽離到獨立的動態(tài)模塊/動態(tài)庫中,與EXE分離存放;
- 主程序通過「動態(tài)加載」方式調(diào)用模塊/動態(tài)庫,不進行靜態(tài)打包;
- 后續(xù)更新僅替換獨立的動態(tài)模塊/動態(tài)庫,無需改動主程序EXE。
選型建議
- 若你是Python開發(fā)者,無C/C++基礎,優(yōu)先選擇方案1(快速落地)或方案2(需要保密時);
- 若你有C/C++基礎,或需要高性能、跨語言對接,優(yōu)先選擇方案3(與C++調(diào)用動態(tài)庫邏輯一致);
- 無論哪種方案,核心原則是「分離固定邏輯和可變邏輯」,這是實現(xiàn)「無需更新EXE」的根本。
到此這篇關于淺析python如何實現(xiàn)類似C++一樣調(diào)用動態(tài)庫的文章就介紹到這了,更多相關python調(diào)用動態(tài)庫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
pycharm不在cmd中運行卻在python控制臺運行問題解決
這篇文章主要介紹了pycharm不在cmd中運行卻在python控制臺運行問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08
python 循環(huán)while和for in簡單實例
下面小編就為大家?guī)硪黄猵ython 循環(huán)while和for in簡單實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-08-08
Python深度學習pytorch卷積神經(jīng)網(wǎng)絡LeNet
這篇文章主要為大家講解了Python深度學習中的pytorch卷積神經(jīng)網(wǎng)絡LeNet的示例解析,有需要的朋友可以借鑒參考下希望能夠有所幫助2021-10-10
python實現(xiàn)將元祖轉(zhuǎn)換成數(shù)組的方法
這篇文章主要介紹了python實現(xiàn)將元祖轉(zhuǎn)換成數(shù)組的方法,涉及Python中l(wèi)ist方法的使用技巧,需要的朋友可以參考下2015-05-05
python3.8.3安裝教程及環(huán)境配置的詳細教程(64-bit)
這篇文章主要介紹了python3.8.3安裝教程及環(huán)境配置的詳細教程(64-bit),本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11
使用Pandoc與Python實現(xiàn)本地化文檔轉(zhuǎn)換全攻略
在DeepSeek等AI工具深度融入工作的今天,我們每天都會生成大量的Markdown格式內(nèi)容,本教程將教你如何使用?Pandoc配合一段簡單的?Python腳本,在本地電腦上免費、安全、完美地完成轉(zhuǎn)換,感興趣的小伙伴可以了解下2026-03-03
Python開發(fā)WebService系列教程之REST,web.py,eurasia,Django
對于今天的WebService開發(fā),我們至少有兩種選擇:SOAP/WSDL/UDDI系列的; REST風格架構(gòu)系列的 ?。。?/div> 2014-06-06最新評論

