Python擴展(pybind11混編)


  • 背景介紹
    pybind11是一個基於C++11標准的模版庫. 與Boost.Python類似, pybind11主要着眼於創建C++代碼的Python封裝, 並為其提供了一套輕量級的解決方案.

  • 安裝與代碼示例
    ①. 安裝C++編譯器(各平台略有不同, 支持C++11標准即可)
    ②. 安裝cmake工具(官網下載安裝即可, 用於組織C++工程)
    ③. 安裝Python解釋器(官網下載安裝即可)
    ④. 安裝pybind11庫
         終端運行: pip3 install pybind11 
    ⑤. 獲取pybind11庫相關目錄
         解釋器內運行:

    import pybind11
    pybind11.get_cmake_dir()     # 獲取cmake目錄
    pybind11.get_include()       # 獲取include目錄

    ⑥. 待封裝之C++源碼
         本文以一個main.cpp源文件為例, 簡要給出一個函數與一個類的封裝示例, 代碼如下,

    #include <string>
    #include <iostream>
    
    #include <pybind11/pybind11.h>
    #include <pybind11/eigen.h>
    
    
    int MyFunc(int i, int j)
    {
        return i + j;
    }
    
    class MyClass
    {
    public:
        MyClass(const std::string& msg) : msg_(msg) {}
    
        void printMsg()
        {
            std::cout << this->msg_ << std::endl;
        }
    
        Eigen::VectorXd add(const Eigen::VectorXd& lhs, const Eigen::VectorXd& rhs)
        {
            Eigen::VectorXd ret = lhs + rhs;
            return ret;
        }
    
        std::string msg_;
    };
    
    PYBIND11_MODULE(testlib, m)     // 此處設置模塊名為testlib
    {
        m.doc() = "This is a test library";
        
        m.def("MyFunc", &MyFunc, "my first function",
            pybind11::arg("i") = 1, pybind11::arg("j") = 2);
    
        pybind11::class_<MyClass>(m, "MyClass")
            .def(pybind11::init<const std::string&>())
            .def("printMsg", &MyClass::printMsg)
            .def("add", &MyClass::add, pybind11::arg("lhs"), pybind11::arg("rhs"))
            .def_readwrite("msg_", &MyClass::msg_);
    }

    其中, MyFunc是待導出函數, MyClass是待導出類. 注意, 上例含eigen庫(C++)與numpy庫(Python)之映射, 無eigen庫的小伙伴可以注釋相關內容.

  • cmake工程示例
    配合上述main.cpp源文件, CMakeLists.txt文件內容如下,

    cmake_minimum_required(VERSION 3.15)
    
    set(CMAKE_BUILD_TYPE "Release")
    set(CMAKE_CXX_STANDARD 11)
    project(test_lib)
    
    set(test_srcs
    main.cpp
    )
    
    set(pybind11_DIR "/opt/homebrew/lib/python3.9/site-packages/pybind11/share/cmake/pybind11")   # 此處設置pybind11之cmake目錄, 即: pybind11.get_cmake_dir()
    find_package(pybind11 REQUIRED)
    pybind11_add_module(testlib ${test_srcs})   # 此處設置模塊名為testlib
    target_include_directories(testlib PUBLIC "/Users/xxhbdk/MyLibs/eigen-3.4.0")   # 此處附加包含eigen庫目錄

    當前工程結構如下,

  • 編譯及效果展示
    終端運行如下命令編譯Python動態庫:

    mkdir build      # 創建編譯目錄
    cd build
    cmake ..
    make

    運行完成后, 筆者build目錄下生成了Python動態庫文件"testlib.cpython-39-darwin.so". 隨后即可在Python環境中使用之, 測試效果如下,

    可以看到, 接口導出整體符合預期.

  • 注意事項
    ①. C++源文件中模塊名需要與cmake工程文件中模塊名保持一致;
    ②. 本文着重闡述pybind11配合cmake之通用流程, 具體API使用細節, 請大家參考官方文檔等資料.

  • 參考文檔
    ①. https://pybind11.readthedocs.io/en/stable/
    ②. https://cmake.org/cmake/help/latest/

  • 補充1(C++調用Python)
    ①. 待調用之Python源碼
         本文以一個my_func.py源文件為例, 簡要給出一個函數示例, 代碼如下,
    def MyFunc(i, j):
        return i + j

    ②. 待編譯之C++源碼
         本文以一個main.cpp源文件為例, 簡要給出一個C++調用Python函數之示例, 代碼如下,

    #include <iostream>
    #include <pybind11/embed.h>
    
    
    int main()
    {
        pybind11::scoped_interpreter guard;     // 初始化python解釋器
    
        pybind11::module my_func = pybind11::module::import("my_func");
        int i = 11;
        int j = 22;
        pybind11::object ret = my_func.attr("MyFunc")(i, j);
        int n = ret.cast<int>();
        std::cout << i << " + " << j << " = " << n << std::endl;
    }

    ③. cmake工程示例
         配合上述main.cpp源文件, CMakeLists.txt文件內容如下,

    cmake_minimum_required(VERSION 3.15)
    
    set(CMAKE_BUILD_TYPE "Release")
    set(CMAKE_CXX_STANDARD 11)
    project(test_cppInvokePy)
    
    set(test_srcs
    main.cpp
    )
    
    add_executable(main ${test_srcs})
    set(pybind11_DIR "/opt/homebrew/lib/python3.9/site-packages/pybind11/share/cmake/pybind11")   # 此處設置pybind11之cmake目錄, 即: pybind11.get_cmake_dir()
    find_package(pybind11 REQUIRED)
    target_link_libraries(main PUBLIC pybind11::embed)

    ④. 編譯及效果展示
         當前工程結構如下,

         終端運行如下命令編譯C++可執行文件,

    mkdir build      # 創建編譯目錄
    cd build
    cmake ..
    make

    運行完成后, 筆者build目錄下生成了可執行文件"main". 隨后將Python文件my_func.py拷貝至此build目錄下.終端運行可執行文件main, 測試效果如下,

    可以看到, 執行結果符合預期, Python模塊調用成功.

  • 補充1 - 參考文檔
    ①. https://www.yuque.com/u461675/pcadi1/hf4fha#e82c4d4d
  • 補充2(Global Interpreter Lock)
    當執行流從Python側進入C++側時, GIL總是持有的. 因此, 如果C++側代碼長時間運行, 且不釋放GIL, 則Python側多線程可能無法達到預期的運行效果(如: UI運行阻塞等).
    因此, 通過Python調用C++時, 若C++側代碼執行時間較長且具備Python側多線程需求, 建議在C++代碼入口處釋放GIL.
    釋放GIL之方法①(功能代碼執行處)pybind11::gil_scoped_release release; 
    釋放GIL之方法②(模塊接口定義處)pybind11::call_guard<pybind11::gil_scoped_release>(); 
  • 補充2 - 注意事項
    ①. C++側多線程不受GIL影響.
  • 補充2 - 參考文檔
    ①. https://pybind11.readthedocs.io/en/stable/advanced/misc.html#global-interpreter-lock-gil


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM