Skip to content

NanoBind 基础绑定

NanoBind是一款轻量级的C++绑定库,旨在简化C++与Python之间的交互。它提供了类似于PyBind11的功能,但更加简洁和高效。

关于PyBind11

NanoBind与PyBind11在功能上有很多相似之处,但NanoBind的设计目标是提供更小的二进制体积和更快的编译速度。

安装 NanoBind

在开始之前,确保你已经安装了NanoBind。

1. 使用Pip安装

安装到当前环境:

bash
pip install nanobind

2. 手动配置

  1. 克隆并自行组织项目结构 本文使用
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
  1. 在 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_ptrstd::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端的实现。

Released under the BSD3 License