本文使用Anaconda環(huán)境進行演示,python版本為3.8,若讀者不使用Anaconda,需自行配置環(huán)境。
Pybind11
是一個純頭文件庫,用于將C++函數(shù)封裝出Python接口。該庫可以很方便地使用conda
進行安裝,執(zhí)行命令conda install pybind11 pytest
淤翔。完成安裝后,我們開始編寫第一個程序佩谷,命名為hello.cpp
旁壮。
// hello.cpp
#include <pybind11/pybind11.h>
#include <iostream>
namespace py = pybind11;
using namespace std;
void say_hello(const char* name)
{
cout << "Hello, " << name << "! This is a message from C++ library!" << endl;
}
PYBIND11_MODULE(hello, m)
{
m.def("say_hello", &say_hello, "Print hello to the name.");
}
上面的cpp文件中,say_hello
為程序函數(shù)谐檀,PYBIND11_MODULE
為包裝函數(shù)抡谐,此二者在實際應(yīng)用中一般會分別存放在不同的文件中,但在本例中為演示需要桐猬,我將他們放在了同一個文件中麦撵。
PYBIND11_MODULE
是一個宏定義,其功能是創(chuàng)建一個函數(shù)溃肪,這個函數(shù)會在Python執(zhí)行import
語句時被調(diào)用免胃,其接受兩個參數(shù),第一個參數(shù)為模塊名稱惫撰,這里我們直接將hello
填入羔沙,稍候可以在Python中使用import hello
導(dǎo)入該模塊;第二個參數(shù)m
是創(chuàng)建Python關(guān)聯(lián)代碼的主接口厨钻,其類型為py::module_
扼雏。module_::def()
用于生成能夠?qū)?code>say_hello函數(shù)暴露給Python的代碼坚嗜,其第一個參數(shù)為字符串,將會成為Python中調(diào)用的函數(shù)名诗充;第二個參數(shù)是C++函數(shù)的引用苍蔬;第三個參數(shù)是說明字符串,在Python中可以使用help(say_hello)
查看蝴蜓。
現(xiàn)在碟绑,我們可以著手編譯這個庫了,這里我使用g++
進行編譯励翼,通過-shared
參數(shù)將其編譯為共享庫,在Linux和MacOS上的編譯命令稍有不同:
- Linux
g++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) hello.cpp -o hello$(python3-config --extension-suffix)
- MacOS
g++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) hello.cpp -o hello$(python3-config --extension-suffix) -undefined dynamic_lookup
在我的系統(tǒng)中辜荠,會生成一個名為hello.cpython-38-darwin.so
的文件汽抚,這就是編譯產(chǎn)生的共享庫文件。現(xiàn)在伯病,在當(dāng)前目錄進入python解釋器造烁,即可導(dǎo)入包并運行函數(shù):
$ python
Python 3.8.12 (default, Oct 12 2021, 06:23:56)
[Clang 10.0.0 ] :: Anaconda, Inc. on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello
>>> hello.say_hello("Tom")
Hello, Tom! This is a message from C++ library!
除了手動編譯外,還可以使用cmake
工具進行跨平臺編譯配置午笛,在當(dāng)前目錄下新建一個CMakeLists.txt
文件惭蟋,然后寫入下面的命令:
cmake_minimum_required(VERSION 3.4...3.18)
project(hello)
find_package(Python COMPONENTS Interpreter Development REQUIRED)
find_package(pybind11 REQUIRED)
pybind11_add_module(hello hello.cpp)
其中,find_package
函數(shù)會自動找到正確的依賴包药磺,pybind11_add_module
函數(shù)添加了一個名為hello
的模塊告组。
保存退出后,執(zhí)行cmake . && make
命令癌佩,即可看到編譯產(chǎn)生的共享庫文件木缝。
使用py::arg
參數(shù)可以將變量名也暴露給python,同時围辙,若有需要我碟,還可以設(shè)置參數(shù)默認值:
PYBIND11_MODULE(hello, m)
{
m.def("say_hello", &say_hello, "Print hello to the name.", py::arg("name")="Jack");
}
Python運行效果:
>>> import hello
>>> hello.say_hello()
Hello, Jack! This is a message from C++ library!
>>> hello.say_hello("Nic")
Hello, Nic! This is a message from C++ library!
>>> hello.say_hello(name="George")
Hello, George! This is a message from C++ library!
>>> help(hello.say_hello)
Help on built-in function say_hello in module hello:
say_hello(...) method of builtins.PyCapsule instance
say_hello(name: str = 'Jack') -> None
Print hello to the name.
(END)
py::arg
參數(shù)還有一種簡化寫法,通過引入名稱空間pybind11::literals
姚建,可以使用_a
后綴來替代py::arg
矫俺,例如,上面的一條m.def
函數(shù)可以寫成:
using namespace pybind11::literals;
PYBIND11_MODULE(hello, m)
{
m.def("say_hello", &say_hello, "name"_a="Jack");
}
除了包裝函數(shù)外掸冤,通過使用module_::attr
函數(shù)可以將C++中的變量暴露給Python厘托,例如下面的C++程序:
// hello.cpp
#include <pybind11/pybind11.h>
#include <iostream>
namespace py = pybind11;
using namespace std;
using namespace pybind11::literals;
const char* where = "This is a message from C++ library!";
const char* cst = "This is an auto cast string by pybind11.";
void say_hello(const char* name)
{
cout << "Hello, " << name << "!" << endl;
}
PYBIND11_MODULE(hello, m)
{
m.def("say_hello", &say_hello, "name"_a="Jack");
m.attr("where") = where;
m.attr("cast") = py::cast(cst);
}
Python中運行效果:
>>> import hello
>>> hello.say_hello("Tom")
Hello, Tom!
>>> print(hello.where)
This is a message from C++ library!
>>> print(hello.cast)
This is an auto cast string by pybind11.
>>> type(hello.cast)
<class 'str'>