NanoBind 基础绑定
NanoBind
是一款轻量级的C++绑定库,旨在简化C++与Python之间的交互。它提供了类似于PyBind11的功能,但更加简洁和高效。
关于PyBind11
NanoBind与PyBind11在功能上有很多相似之处,但NanoBind
的设计目标是提供更小的二进制体积和更快的编译速度。
安装 NanoBind
在开始之前,确保你已经安装了NanoBind。
1. 使用Pip安装
安装到当前环境:
bash
pip install nanobind
2. 手动配置
- 克隆并自行组织项目结构 本文使用
bash
git clone https://github.com/wjakob/nanobind.git
cd {libs}/nanobind
git submodule update --init --recursive
或者以子模块安装(多人项目推荐)
bash
git submodule add https://github.com/wjakob/nanobind ext/nanobind
git submodule update --init --recursive
- 在 CMake 项目中引入 NanoBind:
cmake
add_subdirectory({path}/NanoBind)
编写 CMakeLists.txt
创建一个 CMakeLists.txt
文件来配置你的项目。以下是一个基本的示例:
cmake
cmake_minimum_required (VERSION 3.18)
project(PYTHON_NANO)
if(MSVC)
add_compile_options(/utf-8 /EHsc)
endif()
# 使用CMake(>=3.18)自动寻找特定版本的Python(包含include目录和libs目录)
find_package(Python 3.12 COMPONENTS Interpreter Development.Module REQUIRED)
# 引入NanoBind(请确保路径正确)
add_subdirectory(libs/nanobind)
# 对于pip安装的nanobind可以经过配置后直接find_package
# execute_process(
# COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
# OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT)
# find_package(nanobind CONFIG REQUIRED)
# nanobind_add_module为NanoBind提供的函数,用于创建Python模块
# 本质上是 add_library + target_link_libraries 封装
nanobind_add_module(nanoTest main.cpp)
set_property(TARGET nanoTest PROPERTY CXX_STANDARD 20)
编写绑定代码
创建一个 main.cpp
文件,编写你的C++代码并使用NanoBind进行绑定。
cpp
#include <iostream>
#include <functional>
#include <nanobind/nanobind.h>
#include <nanobind/stl/string.h> // STL string 转换支持
#include <nanobind/stl/function.h> // STL function 转换支持
static int add(int a, int b)
{
return a + b;
}
// C++异常抛出测试(Python端捕获)
static void errTest()
{
throw std::runtime_error("This is a test error");
}
// Python异常捕获测试(C++端捕获)
static void funcErrTest(const std::function<void()>& func)
{
try
{
func();
}
catch (const std::exception& e)
{
std::cerr << "Caught exception: " << e.what() << "\n";
}
}
// STD类型转换测试
static std::string strAdd(const std::string& s1, const std::string& s2)
{
return s1 + s2;
}
// 与pybind11类似,NB_MODULE宏用于定义Python模块
// 第一个参数是模块名称,第二个参数是模块对象
NB_MODULE(nanoTest, m)
{
// 绑定函数
m.def("add", &add);
m.def("errTest", &errTest, "异常测试");
m.def("funcErrTest", &funcErrTest, "函数异常测试");
m.def("strAdd", &strAdd, "字符串拼接测试");
}
代码测试:
python
import nanoTest
print(nanoTest.add(1, 2))
# 自动STD字符串转换
print(nanoTest.strAdd("你好,", "世界!"))
try:
# 可正确捕获C++异常
nanoTest.errTest()
except Exception as e:
print(f"Error caught: {e}")
def testFunc():
raise RuntimeError("This is a python error")
# C++可正确捕获Python异常
nanoTest.funcErrTest(testFunc)
字面量后缀
NanoBind支持C++
的字面量后缀功能,可以简化一写代码编写。
默认形参
通过nanobind::literals
命名空间提供的字面量后缀,可以为函数参数设置默认值。
cpp
#include <iostream>
#include <nanobind/nanobind.h>
namespace nb = nanobind; // 简化命名空间
using namespace nb::literals; // 使用字面量后缀功能
static void argsFunc(int a, int b = 2, int c = 3)
{
std::cout << "a: " << a << ", b: " << b << ", c: " << c << "\n";
}
NB_MODULE(nanoTest, m)
{
// 绑定函数并设置形参以及默认值(nanobind::literals提供_a后缀创建args对象)
m.def("argsFunc", &add, "a"_a, "b"_a = 2, "c"_a = 3);
}
简易class绑定
NanoBind提供了简易的类绑定方式,类似于PyBind11。
定义类
定义一个简单的C++类,并使用NanoBind进行绑定。
cpp
#include <iostream>
#include <nanobind/nanobind.h>
#include <nanobind/stl/string.h>
namespace nb = nanobind;
class Dog
{
public:
std::string mName;
Dog(const std::string& name) : mName(name) {}
std::string bark() const
{
return mName + ": woof!";
}
void setName(const std::string& name)
{
mName = name;
}
std::string getName() const
{
return mName;
}
};
NB_MODULE(nanoTest, m)
{
// 类绑定 方法以及属性
nb::class_<Dog>(m, "Dog")
.def(nb::init<const std::string&>())
.def("bark", &Dog::bark)
.def("setName", &Dog::setName, "设置狗的名字")
// property绑定(大多数情况下可以用def_rw自动生成)
.def_prop_rw("name", &Dog::getName, &Dog::setName)
// def_rw读写属性绑定
.def_rw("mName", &Dog::mName);
}
代码测试:
python
import nanoTest
dog = nanoTest.Dog("小狗")
print(dog.bark())
dog.mName = "大狗"
print(dog.bark())
dog.name = "中狗"
print(dog.bark())
dog.setName("小狗")
print(dog.bark())
智能指针支持
NanoBind支持智能指针的绑定,类似于PyBind11。
cpp
#include <iostream>
#include <memory>
#include <nanobind/nanobind.h>
#include <nanobind/stl/shared_ptr.h> // STL shared_ptr 转换支持
namespace nb = nanobind;
class Resource
{
public:
Resource() { std::cout << "Res created\n"; }
void test() { std::cout << "Res test function called\n"; }
~Resource() { std::cout << "Res destroyed\n"; }
};
class Manager
{
public:
std::shared_ptr<Resource> res;
Manager() : res(std::make_shared<Resource>()) {}
~Manager() { std::cout << "Manager destroyed\n"; }
};
NB_MODULE(nanoTest, m)
{
// 绑定智能指针,也可以通过 is_weak_referenceable 启用弱引用支持(会增大内存开销)
nb::class_<Resource, /* nanobind::is_weak_referenceable() */>(m, "Resource")
.def(nb::init<>())
.def("test", &Resource::test);
nb::class_<Manager>(m, "Manager")
.def(nb::init<>())
.def_rw("res", &Manager::res, "资源对象");
}
代码测试:
python
import nanoTest
# 创建manager
manager = nanoTest.Manager()
# 持有res对象引用
res = manager.res
res.test()
# 删除manager对象(引用归0析构)
del manager
# 仍然可以使用res对象
res.test()
# 删除res对象(引用归0析构)
del res
同理 std::unique_ptr
和std::weak_ptr
等智能指针也可以类似绑定。
多态与虚函数
与 PyBind11
类似,NanoBind支持C++的多态和虚函数绑定。
cpp
#include <iostream>
#include <nanobind/nanobind.h>
#include <nanobind/trampoline.h> // 启用跳板支持
namespace nb = nanobind;
class Entity
{
public:
virtual void update()
{
std::cout << "Entity update called\n";
}
virtual ~Entity() = default;
};
// 跳板类 用于重写虚函数转发调用Python的实现
class PyEntity : public Entity
{
public:
// 使用nanobind的trampoline机制来重写Entity类的方法
NB_TRAMPOLINE(Entity, 1); // 标记有1一个方法需要支持重写
// 重写update方法 转发调用python端实现
void update() override
{
NB_OVERRIDE(update);
}
};
static void updateEntity(Entity& entity)
{
entity.update();
}
NB_MODULE(nanoTest, m)
{
m.def("updateEntity", &updateEntity, "更新实体逻辑");
// 绑定Entity类和PyEntity类 <基类, 跳板转发类>
nb::class_<Entity, PyEntity>(m, "Entity")
.def(nb::init<>())
.def("update", &Entity::update);
}
代码测试:
python
import nanoTest
from nanoTest import Entity
class Zombie(Entity):
def update(self):
super().update() # -> 调用C++基类方法 输出: "Entity update called"
print("Zombie is updating...") # -> Python端其他实现
zombie = Zombie()
nanoTest.updateEntity(zombie)
关于跳板
如果不使用跳板转发调用Python实现,那么C++测虚函数调用将不会触发Python端的实现。