上一篇示範從檔案載入模組。本文解決更靈活的需求:
.py 檔案核心 API:Py_CompileString + PyImport_ExecCodeModule
# call.py (業務邏輯模組)
def test():
print('hello world')
def add(a, b):
return a + b
# api.py (載入器模組)
import io
def load_test():
with open('call.py', 'r') as fp:
return fp.read() # 回傳原始碼字串
#include <stdio.h>
#include <stdlib.h>
#include <Python.h>
int main(int argc, char *argv[]) {
Py_Initialize();
if (!Py_IsInitialized()) return -1;
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
/* 1. 載入 api 模組並呼叫 load_test() 取得原始碼字串 */
PyObject *pName = PyUnicode_FromString("api");
PyObject *pModule = PyImport_Import(pName);
Py_DECREF(pName);
if (!pModule) { PyErr_Print(); Py_Finalize(); return -1; }
PyObject *pDict = PyModule_GetDict(pModule);
PyObject *pFunc = PyDict_GetItemString(pDict, "load_test");
if (!pFunc || !PyCallable_Check(pFunc)) { Py_DECREF(pModule); Py_Finalize(); return -1; }
PyObject *pCodeStr = PyObject_CallObject(pFunc, NULL); // 回傳 Python str 物件
if (!pCodeStr) { Py_DECREF(pModule); Py_Finalize(); return -1; }
const char *c_code = PyUnicode_AsUTF8(pCodeStr); // 轉為 C 字串
if (!c_code) { Py_DECREF(pCodeStr); Py_DECREF(pModule); Py_Finalize(); return -1; }
/* 2. 核心步驟:編譯字串為程式碼物件 */
PyObject *pCodeObj = Py_CompileString(c_code, "", Py_file_input);
Py_DECREF(pCodeStr);
if (!pCodeObj) { PyErr_Print(); Py_DECREF(pModule); Py_Finalize(); return -1; }
/* 3. 執行程式碼物件建立模組 */
PyObject *pNewModule = PyImport_ExecCodeModule("dynamic_call", pCodeObj);
Py_DECREF(pCodeObj);
if (!pNewModule) { PyErr_Print(); Py_DECREF(pModule); Py_Finalize(); return -1; }
/* 4. 從動態模組取得 add 函數並呼叫 */
PyObject *pNewDict = PyModule_GetDict(pNewModule);
pFunc = PyDict_GetItemString(pNewDict, "add");
if (pFunc && PyCallable_Check(pFunc)) {
PyObject *pArgs = Py_BuildValue("(ll)", 3, 4);
PyObject *pResult = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pArgs);
if (pResult) {
long ret = PyLong_AsLong(pResult);
printf("動態載入 add(3,4) = %ld\n", ret);
Py_DECREF(pResult);
}
}
/* 5. 清理 */
Py_DECREF(pNewModule);
Py_DECREF(pModule);
Py_Finalize();
return 0;
}
| API | 用途 | 關鍵參數 |
|---|---|---|
Py_CompileString | 將字串編譯為程式碼物件 | str: 原始碼filename: 錯誤訊息顯示用虛擬檔名start: Py_file_input (模組級)、Py_eval_input (單一運算式)、Py_single_input (互動式單句) |
PyImport_ExecCodeModule | 執行程式碼物件並註冊為模組 | name: 模組名 (如 "dynamic_call")co: Py_CompileString 回傳的程式碼物件 |
PyImport_ExecCodeModuleEx 與 PyImport_ExecCodeModuleWithPathnamesPyImport_ExecCodeModuleEx(name, co, pathname):指定 __file__ 屬性PyImport_ExecCodeModuleWithPathnames(name, co, pathname, cpathname):同時指定原始碼與編譯後路徑(Python 3.2+).py 文字,動態載入為插件模組PyDict_SetItemString 限制可用內建函數/模組CompileString + ExecCodeModule 替換 sys.modules 中舊模組Py_CompileString 回傳新引用,用完必須 Py_DECREFPyImport_ExecCodeModule 回傳新引用(模組物件)PyUnicode_AsUTF8 回傳借用引用指標,不可 Py_DECREF,且生命週期綁定原 PyObject*/* 編譯失敗時獲取詳細錯誤 */
if (!pCodeObj) {
PyErr_Print(); // 印出語法錯誤、縮排錯誤等
// 或程式化獲取
PyObject *ptype, *pvalue, *ptraceback;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
// 解析 pvalue 獲取錯誤訊息字串
}