一、說明
????????Python是一種高級(jí)編程語言,它可以調(diào)用其他語言編寫的函數(shù)。在 Python 中調(diào)用 C 函數(shù)的方法有兩種:1)使用 Python 提供的 ctypes 庫;2)使用 Python 提供的 Cython 庫。
????????注意:您可以在此存儲(chǔ)庫中下載此示例的完整代碼。如果您對(duì)本文有任何意見,可以在那里開始一個(gè)問題,或與我聯(lián)系。
二、PyBind11 vs ctypes
????????基本上有兩種方法可以從 Python 調(diào)用C++:使用 PyBind11 C++ 庫生成 Python 模塊,或者使用 cytpes Python 包訪問編譯的共享庫。使用 PyBind11,我們可以更輕松地共享許多數(shù)據(jù)類型,而使用?ctypes 是一種低級(jí)的 C 樣式解決方案。
????????就我而言,我希望能夠利用C++性能和可移植性,但我不想放棄解釋語言的交互性以進(jìn)行快速探索和調(diào)試。
????????幸運(yùn)的是,從Python調(diào)用C++并不像一開始看起來那么困難。這樣,我們可以在開發(fā)C++代碼的同時(shí)掌握 Python 的一些交互性。
就我而言,我想使用 Python 來:
- 將一些問題參數(shù)傳遞給C++
- 調(diào)用C++代碼以運(yùn)行計(jì)算密集型例程
- 檢索最終結(jié)果,以及一些用于調(diào)試的中間計(jì)算。
- 以交互方式瀏覽結(jié)果,并生成繪圖和報(bào)告。
????????使用 ctypes 的問題在于,共享許多數(shù)據(jù)類型需要相當(dāng)多的低級(jí)解決方法。例如,雖然ctypes不支持復(fù)數(shù)等基本的東西,但PyBind11使Numpy與Eigen完全互操作,需要最少的代碼。
????????但是,我也發(fā)現(xiàn)了?PyBind11 的小問題。事實(shí)證明,在重新編譯C++代碼并嘗試重新加載 PyBind 生成的 Python 模塊后,什么也沒發(fā)生。重新加載編譯模塊的唯一方法是重新啟動(dòng)我的 Python 會(huì)話。無論如何,這沒什么大不了的,因?yàn)镻ython的啟動(dòng)時(shí)間可以忽略不計(jì)。而且,此步驟可能在 IDE 級(jí)別自動(dòng)執(zhí)行。
????????因此,現(xiàn)在的問題是如何充分利用 PyBind11。
三、與 PyBind11 共享C++類
????????PyBind11 的官方文檔非常出色,我可以毫無問題地開始使用它。但是,我想分享這個(gè)庫的超級(jí)快速入門指南,以及我打算如何使用它。
????????Pybind11 是一個(gè)僅標(biāo)頭庫,你可以通過以下方式獲取它:
pip install pybind11
????????雖然沒有必要將所有C++代碼構(gòu)建為一個(gè)類,但如果你有一個(gè)類要在C++和 Python 之間共享,Pybind11 會(huì)讓你的事情變得非常容易。(其實(shí)我更像是那種人,總是想介紹給定項(xiàng)目中最少的類數(shù))vector
struct
????????然而,在這種情況下,我發(fā)現(xiàn)使用外觀設(shè)計(jì)模式(參見wiki)可以同時(shí)導(dǎo)致非常簡(jiǎn)單的Python/C++互操作性和一個(gè)不錯(cuò)的API。
????????所以,我想出了一個(gè)簡(jiǎn)單的課程。它基本上包含:
- 讀取問題參數(shù)的構(gòu)造函數(shù)。
- 執(zhí)行計(jì)算的函數(shù)。
run()
- 一些數(shù)組作為公共變量來存儲(chǔ)結(jié)果。
Eigen
????????這是我的最小示例:
// mylib.h
#include <Eigen/Dense>
#include <cmath>
using Eigen::Matrix, Eigen::Dynamic;
typedef Matrix<std::complex<double>, Eigen::Dynamic, Eigen::Dynamic> myMatrix;
class MyClass {
int N;
double a;
double b;
public:
Eigen::VectorXd v_data;
Eigen::VectorXd v_gamma;
MyClass(){}
MyClass( double a_in, double b_in, int N_in)
{
N = N_in;
a = a_in;
b = b_in;
}
void run()
{
v_data = Eigen::VectorXd::LinSpaced(N, a, b);
auto gammafunc = [](double it) { return std::tgamma(it); };
v_gamma = v_data.unaryExpr(gammafunc);
}
};
????????要共享這個(gè)類,我們需要添加一些C++代碼。我寧愿在一個(gè)單獨(dú)的文件中執(zhí)行此操作,其中包含創(chuàng)建 python 包裝器所需的一切。
// pywrap.cpp
#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>
#include "mylib.h"
namespace py = pybind11;
constexpr auto byref = py::return_value_policy::reference_internal;
PYBIND11_MODULE(MyLib, m) {
m.doc() = "optional module docstring";
py::class_<MyClass>(m, "MyClass")
.def(py::init<double, double, int>())
.def("run", &MyClass::run, py::call_guard<py::gil_scoped_release>())
.def_readonly("v_data", &MyClass::v_data, byref)
.def_readonly("v_gamma", &MyClass::v_gamma, byref)
;
}
????????需要強(qiáng)調(diào)的幾點(diǎn):
- 類構(gòu)造函數(shù)簽名指定為
.def(py::init<int, double, double>())
- 對(duì)于函數(shù),我們希望釋放 GIL(全局解釋器鎖),這將阻止我們的函數(shù)使用多個(gè)線程。
run()
????????最后,可以使用以下文件進(jìn)行編譯:CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyLib)
set(CMAKE_CXX_STANDARD 20)
set(PYBIND11_PYTHON_VERSION 3.6)
set(CMAKE_CXX_FLAGS "-Wall -Wextra -fPIC")
find_package(pybind11 REQUIRED)
find_package(Eigen3 REQUIRED)
pybind11_add_module(${PROJECT_NAME} pywrap.cpp)
target_compile_definitions(${PROJECT_NAME} PRIVATE VERSION_INFO=${EXAMPLE_VERSION_INFO})
target_include_directories(${PROJECT_NAME} PRIVATE ${PYBIND11_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} PRIVATE Eigen3::Eigen)
????????現(xiàn)在你已經(jīng)準(zhǔn)備好了。如果您使用的是 VS Code,則在配置 CMake 擴(kuò)展后,只需按 F7 即可編譯C++庫。
四、從 Python 調(diào)用C++庫
????????這個(gè)過程非常簡(jiǎn)單,應(yīng)該開箱即用。但是,有幾個(gè)步驟可以優(yōu)化交互式工作流,這些步驟稍微棘手一些,也值得實(shí)施。
????????例如,如果您正在執(zhí)行 Python 環(huán)境并且您的編譯庫進(jìn)入一個(gè)目錄,您可以執(zhí)行以下操作:build
import sys
sys.path.append("build/")
from MyLib import MyClass
import matplotlib.pyplot as plt
Simulation = MyClass(-4,4,1000)
Simulation.run()
plt.plot(Simulation.v_data, Simulation.v_gamma, \
"--", linewidth = 3, color=(1,0,0,0.6),label="Function Value")
plt.ylim(-10,10)
plt.xlabel("x")
plt.ylabel("($f(x) = \gamma(x)$)")
plt.title("(Gamma Function: $\gamma(z) = \int_0^\infty x^{z-1} e^{-x} dx$)",fontsize = 18);
plt.show()
????????請(qǐng)注意,特征向量會(huì)自動(dòng)轉(zhuǎn)換為?Python 數(shù)組。
????????Ater 修改 ,每個(gè)我們要公開的新函數(shù)或變量只需要添加一行代碼。myLib.hpp
pywrap.cpp
????????不幸的是,這不會(huì)帶來完全交互式的工作流程。當(dāng)您在更改后重新編譯C++代碼時(shí),Python 端不會(huì)發(fā)生任何事情。即使您嘗試使用以下方法重新加載 Python 模塊:importtools
import importlib
importlib.reload(MyLib)
????????什么也沒發(fā)生。原因是編譯后的代碼無法在 Python 中重新加載。
????????因此,在使用 PyBind11 時(shí),每次重新編譯C++代碼時(shí)都需要重新啟動(dòng) Python 會(huì)話,我覺得這對(duì)于開發(fā)目的來說有點(diǎn)煩人。但是,這是一個(gè)很小的代價(jià),因?yàn)镻ython的啟動(dòng)時(shí)間可以忽略不計(jì),并且可能有一種方法可以使用一些IDE熱鍵或其他工具使該過程自動(dòng)化。
五、總結(jié)
????????因此,這就是您可以輕松地從 Python 調(diào)用C++庫的方式。
????????特別是,這個(gè)兩步過程可以產(chǎn)生一個(gè)非?;?dòng)的開發(fā)工作流程。盡管我們有一個(gè)編輯-編譯-運(yùn)行工作流,但我們?cè)谧詈筇砑恿艘粋€(gè)解釋器,所以現(xiàn)在我們的工作流看起來像編輯-編譯-運(yùn)行-探索。文章來源:http://www.zghlxwxcb.cn/news/detail-703180.html
????????將來,我計(jì)劃將兩個(gè)功能合并到此工作流中:文章來源地址http://www.zghlxwxcb.cn/news/detail-703180.html
- 第一個(gè)是C++20模塊,它應(yīng)該加快大型C++項(xiàng)目的編譯時(shí)間。不幸的是,CMake 仍然與模塊不兼容(有關(guān)更新,請(qǐng)參閱此問題),顯然人們必須依靠像?Ninja-Build?這樣的構(gòu)建系統(tǒng)才能立即提供此功能。
- 另一件事是解決在重新編譯C++代碼后(手動(dòng))重新啟動(dòng) Python 會(huì)話的需要。為此,我希望也許可以在VSCode級(jí)別對(duì)此做點(diǎn)什么。到目前為止,VS Code 中的最佳選項(xiàng)似乎是終止 Python 會(huì)話,然后使用 執(zhí)行 Python 代碼,如果尚未打開會(huì)話,則會(huì)創(chuàng)建一個(gè)新會(huì)話。
Shift+Enter
到了這里,關(guān)于如何從 Python 調(diào)用C++的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!