簡介
PyBind11是能夠讓C++和Python代碼之間相互調用的輕量級頭文件庫。在這之前已經有了一個類似功能的庫:Boost.Python。既然已經有了一個類似庫,而且PyBind11的目的和語法都與Boost.Python相似,爲什麼還要重複造輪子?原因主要有以下亮點:
- Boost.Python爲了兼容大多數C++標準和編譯器,它使用了很多可以說是魔法的操作去解決問題而變得非常的臃腫;
- 目前很多編譯器對C++11已經有很好的支持,而且C++11應用也比較廣泛。
因此,PyBind11應運而生,他能在拋棄Boost.Python的負擔同時又具備Boost.Python的簡單操作。
使用
PyBind11的主要目的是將已有的C++代碼接口暴露給Python去調用。例如,ONNX Runtime --一個用於ONNX格式的神經網絡模型推理的引擎,其推理的核心模塊是用C++寫的,但是從易用性、Python AI 方面的主導地位等方面考慮,它需要將模型推理的接口暴露給Python。在之前的文章ONNX Runtime 源碼閱讀:模型推理過程概覽中也有提到過。其接口暴露代碼在$ONNX_RUNTIME/onnxruntime/python/onnxruntime_pybind_state.cc中。
將C++暴露給Python主要有兩個大方向:
- 將函數暴露給Python;
- 將類暴露給Python.
暴露函數
例如,我們已經有了一個C++函數實現了一個算法,代碼存放在一個名字叫existence.h
的頭文件中,Python想直接調用它。
using namespace std;
int add(int arg1, int arg2) {
cout<< "value of arg1 is : " << arg1 << endl;
return arg1 + arg2;
}
那麼只需要將PyBind11的一個頭文件包含並使用宏PYBIND11_MODULE
,簡單幾句話就能實現我們的目的:
#include "existence.h"
#include <pybind11/pybind11.h>
PYBIND11_MODULE(example, m) {
m.doc() = "pybind11 example plugin"; // optional module docstring
m.def("add", &add, "A function which adds two numbers");
}
在上面例子中,我們在使用宏的時候給了兩個宏參數:example
和m
,其中example
是你給你的模塊其的名字,m
其實是一個pybind11::module
類的一個實例,這是怎麼做到的呢?下次有機會在解釋。現在我們知道,使用pybind11::module
的方法def
就能將函數暴露,或者說,在名字叫example
的Python模塊定義了一個函數。
當然,目前只是向模塊添加了一個函數,我們還希望它表現的像直接用Python寫的一個函數。什麼意思呢?我們知道,在Python中定義一個函數,在指定它的參數的時候,可以是關鍵字參數、指定參數默認值,特別是參數多的時候,這兩個功能是特別又用的。但是如果我們像上面一樣,使用關鍵字的話,會得到以下錯誤:
>>> example.add(arg1=1,3)
File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg
爲了能讓參數成爲關鍵字參數,我們這樣添加方法到模塊:
m.def("add", &add, "A function which adds two numbers", py::arg("arg1"), py::arg("arg2"));
這樣就好了。
>>> example.add(arg1=4, arg2=5)
value of arg1 is : 4
9
同理,指定參數默認值方法爲:
m.def("add", &add, "A function which adds two numbers", py::arg("arg1") = 1, py::arg("arg2") = 2);
另外,值得一提的是,pybind11::module
中有三個主要的方法,我們已經看到了兩個,分別是doc()
,用於爲模塊添加註釋;def()
,用於給模塊添加方法。另外還有的就是爲模塊添加屬性。例如我們想爲這個模塊添加一個字符串屬性,數姓名就叫who
,保存的是模塊的名字,我們可以使用attr()
方法:
m.attr("who") = "I'm Bai Feifei.";
>>> import example
>>> example.who
u"I'm Bai Feifei."
>>>
暴露類
除了暴露C++方法給Python,另外一個作用就是暴露類。因爲C++和Python都支持面向對象編程,而類是面向對象的核心。
暴露類我們在ONNX Runtime的源代碼中已經看到了活生生的使用場景,與使用宏來暴露模塊函數類似,PyBind11使用一個模板類pybind11::class_
來暴露類。
我們來看ONNX Runtime的使用:
py::class_<InferenceSession>(m, "InferenceSession", R"pbdoc(This is the main class used to run a model.)pbdoc")
.def(
"load_model", [](InferenceSession* sess, std::vector<std::string>& provider_types) {
OrtPybindThrowIfError(sess->Load());
InitializeSession(sess, provider_types);
},
R"pbdoc(Load a model saved in ONNX format.)pbdoc");
同樣的,這個模板類的具體實例也是通過調用一些特殊的方法將方法、屬性等添加到類中,例如它也是通過def()
函數添加類方法,下面列出了主要的方法和用途:
- def():添加方法到類中;
- def_readwrite():添加屬性變量到類中;
- def_readonly() :添加屬性常量;
- def_readwrite_static():添加靜態變量;
- def_readonly_static():添加靜態常量;
這些方法的返回都是py::class_<InferenceSession>
自身,所以可以鏈式調用。
使用def()
添加類方法的時候,對於關鍵字參數和默認參數的定義,與上一節說過的添加木塊函數的方法是一樣的,需要注意的是以下兩點: - 類初始化,Python中使用
__init__()
去對實例進行初始化,因此需要給def
傳遞一個特殊的方法pybind11::init()
去指示如何進行類的初始化工作,如下面例子所示; - 函數重載,因爲Python中是沒有函數重載這種說法的。解決這個問題的方法就是將重載的函數變成函數指針,例如下面這個例子,第一種方法肯定是無法編譯的,要使用第二種方法:
struct Pet {
Pet(const std::string &name, int age) : name(name), age(age) { }
void set(int age_) { age = age_; }
void set(const std::string &name_) { name = name_; }
std::string name;
int age;
};
// 方法一
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string &, int>())
.def("set", &Pet::set, "Set the pet's age")
.def("set", &Pet::set, "Set the pet's name");
// 方法二
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string &, int>())
.def("set", (void (Pet::*)(int)) &Pet::set, "Set the pet's age")
.def("set", (void (Pet::*)(const std::string &)) &Pet::set, "Set the pet's name");
編譯
編譯就簡單了,給出一條命令
c++ -O3 -Wall -shared -std=c++11 -fPIC -I//home/zhou/repos/pybind11/include py_export.cpp -I/home/zhou/venvs/common/include/python2.7 -o example.so
只需要對-I
參數和文件名作修改就可以編譯通過,關於編譯命令的介紹,可以參看之前的文章GCC 命令簡明教程。當然,對於複雜結構的代碼,可以使用make工具來構建,或者使用cmake,但這已經不是本文所討論的範疇,這裏就不展開了。
總結
本文只是簡單介紹了PyBind11的用法,如果需要用到一些更高級的功能或者想更深入瞭解PyBind11的用法,請參閱參考文檔。
下一篇會深入PyBind11的源碼,一探究竟。
References
https://buildmedia.readthedocs.org/media/pdf/pybind11/master/pybind11.pdf
簡介
PyBind11是能夠讓C++和Python代碼之間相互調用的輕量級頭文件庫。在這之前已經有了一個類似功能的庫:Boost.Python。既然已經有了一個類似庫,而且PyBind11的目的和語法都與Boost.Python相似,爲什麼還要重複造輪子?原因主要有以下亮點:
- Boost.Python爲了兼容大多數C++標準和編譯器,它使用了很多可以說是魔法的操作去解決問題而變得非常的臃腫;
- 目前很多編譯器對C++11已經有很好的支持,而且C++11應用也比較廣泛。
因此,PyBind11應運而生,他能在拋棄Boost.Python的負擔同時又具備Boost.Python的簡單操作。
使用
PyBind11的主要目的是將已有的C++代碼接口暴露給Python去調用。例如,ONNX Runtime --一個用於ONNX格式的神經網絡模型推理的引擎,其推理的核心模塊是用C++寫的,但是從易用性、Python AI 方面的主導地位等方面考慮,它需要將模型推理的接口暴露給Python。在之前的文章ONNX Runtime 源碼閱讀:模型推理過程概覽中也有提到過。其接口暴露代碼在$ONNX_RUNTIME/onnxruntime/python/onnxruntime_pybind_state.cc中。
將C++暴露給Python主要有兩個大方向:
- 將函數暴露給Python;
- 將類暴露給Python.
暴露函數
例如,我們已經有了一個C++函數實現了一個算法,代碼存放在一個名字叫existence.h
的頭文件中,Python想直接調用它。
using namespace std;
int add(int arg1, int arg2) {
cout<< "value of arg1 is : " << arg1 << endl;
return arg1 + arg2;
}
那麼只需要將PyBind11的一個頭文件包含並使用宏PYBIND11_MODULE
,簡單幾句話就能實現我們的目的:
#include "existence.h"
#include <pybind11/pybind11.h>
PYBIND11_MODULE(example, m) {
m.doc() = "pybind11 example plugin"; // optional module docstring
m.def("add", &add, "A function which adds two numbers");
}
在上面例子中,我們在使用宏的時候給了兩個宏參數:example
和m
,其中example
是你給你的模塊其的名字,m
其實是一個pybind11::module
類的一個實例,這是怎麼做到的呢?下次有機會在解釋。現在我們知道,使用pybind11::module
的方法def
就能將函數暴露,或者說,在名字叫example
的Python模塊定義了一個函數。
當然,目前只是向模塊添加了一個函數,我們還希望它表現的像直接用Python寫的一個函數。什麼意思呢?我們知道,在Python中定義一個函數,在指定它的參數的時候,可以是關鍵字參數、指定參數默認值,特別是參數多的時候,這兩個功能是特別又用的。但是如果我們像上面一樣,使用關鍵字的話,會得到以下錯誤:
>>> example.add(arg1=1,3)
File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg
爲了能讓參數成爲關鍵字參數,我們這樣添加方法到模塊:
m.def("add", &add, "A function which adds two numbers", py::arg("arg1"), py::arg("arg2"));
這樣就好了。
>>> example.add(arg1=4, arg2=5)
value of arg1 is : 4
9
同理,指定參數默認值方法爲:
m.def("add", &add, "A function which adds two numbers", py::arg("arg1") = 1, py::arg("arg2") = 2);
另外,值得一提的是,pybind11::module
中有三個主要的方法,我們已經看到了兩個,分別是doc()
,用於爲模塊添加註釋;def()
,用於給模塊添加方法。另外還有的就是爲模塊添加屬性。例如我們想爲這個模塊添加一個字符串屬性,數姓名就叫who
,保存的是模塊的名字,我們可以使用attr()
方法:
m.attr("who") = "I'm Bai Feifei.";
>>> import example
>>> example.who
u"I'm Bai Feifei."
>>>
暴露類
除了暴露C++方法給Python,另外一個作用就是暴露類。因爲C++和Python都支持面向對象編程,而類是面向對象的核心。
暴露類我們在ONNX Runtime的源代碼中已經看到了活生生的使用場景,與使用宏來暴露模塊函數類似,PyBind11使用一個模板類pybind11::class_
來暴露類。
我們來看ONNX Runtime的使用:
py::class_<InferenceSession>(m, "InferenceSession", R"pbdoc(This is the main class used to run a model.)pbdoc")
.def(
"load_model", [](InferenceSession* sess, std::vector<std::string>& provider_types) {
OrtPybindThrowIfError(sess->Load());
InitializeSession(sess, provider_types);
},
R"pbdoc(Load a model saved in ONNX format.)pbdoc");
同樣的,這個模板類的具體實例也是通過調用一些特殊的方法將方法、屬性等添加到類中,例如它也是通過def()
函數添加類方法,下面列出了主要的方法和用途:
- def():添加方法到類中;
- def_readwrite():添加屬性變量到類中;
- def_readonly() :添加屬性常量;
- def_readwrite_static():添加靜態變量;
- def_readonly_static():添加靜態常量;
這些方法的返回都是py::class_<InferenceSession>
自身,所以可以鏈式調用。
使用def()
添加類方法的時候,對於關鍵字參數和默認參數的定義,與上一節說過的添加木塊函數的方法是一樣的,需要注意的是以下兩點: - 類初始化,Python中使用
__init__()
去對實例進行初始化,因此需要給def
傳遞一個特殊的方法pybind11::init()
去指示如何進行類的初始化工作,如下面例子所示; - 函數重載,因爲Python中是沒有函數重載這種說法的。解決這個問題的方法就是將重載的函數變成函數指針,例如下面這個例子,第一種方法肯定是無法編譯的,要使用第二種方法:
struct Pet {
Pet(const std::string &name, int age) : name(name), age(age) { }
void set(int age_) { age = age_; }
void set(const std::string &name_) { name = name_; }
std::string name;
int age;
};
// 方法一
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string &, int>())
.def("set", &Pet::set, "Set the pet's age")
.def("set", &Pet::set, "Set the pet's name");
// 方法二
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string &, int>())
.def("set", (void (Pet::*)(int)) &Pet::set, "Set the pet's age")
.def("set", (void (Pet::*)(const std::string &)) &Pet::set, "Set the pet's name");
編譯
編譯就簡單了,給出一條命令
c++ -O3 -Wall -shared -std=c++11 -fPIC -I//home/zhou/repos/pybind11/include py_export.cpp -I/home/zhou/venvs/common/include/python2.7 -o example.so
只需要對-I
參數和文件名作修改就可以編譯通過,關於編譯命令的介紹,可以參看之前的文章GCC 命令簡明教程。當然,對於複雜結構的代碼,可以使用make工具來構建,或者使用cmake,但這已經不是本文所討論的範疇,這裏就不展開了。
總結
本文只是簡單介紹了PyBind11的用法,如果需要用到一些更高級的功能或者想更深入瞭解PyBind11的用法,請參閱參考文檔。
下一篇會深入PyBind11的源碼,一探究竟。
References
https://buildmedia.readthedocs.org/media/pdf/pybind11/master/pybind11.pdf
本文首發於個人微信公衆號TensorBoy。如果你覺得內容還不錯,歡迎分享並關注我的微信公衆號TensorBoy,掃描下方二維碼獲取更多精彩原創內容!