Porting guide¶
The API of nanobind is extremely similar to that of pybind11, which makes porting existing projects easy. Parts of the nanobind documentation are almost verbatim copies pybind11’s documentation.
A number of noteworthy changes are documented below.
Namespace¶
nanobind types and functions are located in the nanobind namespace. The
following shorthand alias is recommended and used throughout the documentation:
namespace nb = nanobind;
Name changes¶
The following macros, types, and functions were renamed:
pybind11 |
nanobind |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Porting one binding at a time¶
A large project can move over gradually, with pybind11 and nanobind bindings
living side by side in the same extension. In that case PYBIND11_MODULE
still provides the entry point, and the setup work that NB_MODULE
normally performs must happen explicitly through
register_module():
PYBIND11_MODULE(my_ext, m) {
if (!nb::register_module(m.ptr()))
throw py::error_already_set();
nb::module_ nb_m = nb::borrow<nb::module_>(m.ptr());
nb::class_<MyType>(nb_m, "MyType");
// ... remaining pybind11 bindings ...
}
Call this before any other part of the nanobind API. Note that the two frameworks do not know about each other’s types: a function bound with nanobind cannot accept a type bound with pybind11, and vice versa. Types that cross this boundary must move together.
None/null arguments¶
In contrast to pybind11, nanobind does not permit None-valued function
arguments by default. You must enable them explicitly using the
arg::none() argument annotation, e.g.:
m.def("func", &func, "arg"_a.none());
It is also possible to set a None default value, in which case the
.none() annotation can be omitted.
m.def("func", &func, "arg"_a = nb::none());
None-valued arguments are only supported by two of the three parameter
passing styles described in the section on information exchange. In particular, they are supported by bindings
and wrappers, but not by type casters.
Custom constructors¶
In pybind11, custom constructors (i.e. ones that do not already exist in the C++ class) could be specified as a lambda function returning an instance of the desired type.
py::class_<MyType>(m, "MyType")
.def(py::init([](int) { return MyType(...); }));
Unfortunately, the implementation of this feature was quite complex and often required further internal calls to the move or copy constructor. nanobind instead reverts to how pybind11 originally implemented this feature using in-place construction (placement new):
nb::class_<MyType>(m, "MyType")
.def("__init__", [](MyType *t) { new (t) MyType(...); });
The provided lambda function will be called with a pointer to uninitialized memory that has already been allocated (this memory region is co-located with the Python object for reasons of efficiency). The lambda function can then either run an in-place constructor and return normally (in which case the instance is assumed to be correctly constructed) or fail by raising an exception.
To turn an existing factory function into a constructor, you will need to combine the above pattern with an invocation of the move/copy-constructor, e.g.:
nb::class_<MyType>(m, "MyType")
.def("__init__", [](MyType *t) { new (t) MyType(MyType::create()); });
Implicit conversions¶
In pybind11, implicit conversions were specified using a follow-up function call. This also works in nanobind, but it is recommended that you already specify them within the constructor declaration:
pybind11:
py::class_<MyType>(m, "MyType")
.def(py::init<MyOtherType>());
py::implicitly_convertible<MyOtherType, MyType>();
nanobind:
nb::class_<MyType>(m, "MyType")
.def(nb::init_implicit<MyOtherType>());
Trampoline classes¶
Trampolines, i.e., polymorphic class implementations that forward virtual
function calls to Python, now require an extra
NB_TRAMPOLINE(parent) declaration, where
parent refers to the parent class.
The macro PYBIND11_OVERRIDE_*(..) required the base type and return value
as the first two arguments. This information is no longer needed in nanobind,
and the arguments should be removed in NB_OVERRIDE_*():
An example:
struct PyAnimal : Animal {
NB_TRAMPOLINE(Animal);
std::string name() const override {
NB_OVERRIDE(name);
}
};
Iterator bindings¶
Use of the nb::make_iterator(),
nb::make_key_iterator(), and
nb::make_value_iterator() functions requires
including the additional header file nanobind/make_iterator.h. The
interface of these functions has also slightly changed: all take a Python scope
and a name as first and second arguments, which are used to permanently
“install” the iterator type (which is created on demand). See the test suite
for a worked out example.
Type casters¶
The API of custom type casters has changed significantly. The following changes are needed:
load()was renamed tofrom_python(). The function now takes an extrauint32_t flagsparameter (insteadbool convert, which is now represented by the flagnb::detail::cast_flags::convert). Acleanup_list *pointer keeps track of Python temporaries that are created by the conversion, and which need to be deallocated after a function call has taken place.flagsandcleanupshould be passed to any recursive usage oftype_caster::from_python(). If casting fails due to a Python exception, the function should clear it (PyErr_Clear()) and returnfalse. If a severe error condition arises that should be reported, use Python warning API calls for this, e.g.PyErr_WarnFormat().cast()was renamed tofrom_cpp(). The function takes a return value policy (as before) and acleanup_list *pointer. If casting fails due to a Python exception, the function should leave the error set (note the asymmetry compared tofrom_python()) and returnnullptr.
Note that the cleanup list is only available when from_python() or
from_cpp() are called as part of function dispatch, while usage by
nb::cast() may set cleanup to nullptr if implicit
conversions are not enabled. This case should be handled gracefully by refusing
the conversion if the cleanup list is absolutely required.
Type casters may not raise C++ exceptions. Both from_python() and
from_cpp() must be annotated with noexcept. Exceptions or failure
conditions caused by a conversion should instead be caught within the
function body and handled as follows:
from_python(): returnfalse. That’s it. (Failed Python to C++ conversions occur all the time while dispatching calls, causing nanobind to simply move to the next function overload. Dedicated error reporting would add undesirable overheads). In case of a severe internal error, use the CPython warning API (e.g.,PyErr_Warn()) to notify the user.from_cpp(): a failure here is more serious, since a return value of a successfully evaluated function cannot be converted, causing the call to fail. To provide further detail, use the CPython error API (e.g.,PyErr_SetString()orPyErr_Format()) and return an invalid handle (return nb::handle();).
The std::pair<T1, T2> type caster (link)
may be useful as a starting point of custom implementations.
Removed features¶
A number of pybind11 features are unavailable in nanobind. The list below uses the following symbols:
○: This removal is a design choice. Use pybind11 if you need this feature.
●: Unclear, to be discussed.
Removed features include:
○ Multiple inheritance: this feature was a persistent source of complexity in pybind11 and it is one of the main casualties in creating nanobind.
○ Holders: nanobind instances co-locate instance data with a Python object instead of accessing it via a holder type. This is a major difference compared to pybind11 and will require changes to binding code that used custom holders (e.g. unique or shared pointers). The intermediate and advanced sections on object ownership provide further detail.
○ Multi-intepreter, Embedding: The ability to embed Python in an executable or run several independent Python interpreters in the same process is unsupported. Nanobind caters to bindings only. Multi-interpreter support would require TLS lookups for nanobind data structures, which is undesirable.
○ Function binding annotations: The
pos_onlyargument annotation was removed. However, the same behavior can be achieved by creating unnamed arguments; see the discussion in the section on keyword-only arguments.○ Metaclasses: creating types with custom metaclasses is unsupported.
○ Module-local bindings: support was removed (both for types and exceptions).
○ Custom allocation: C++ classes with an overloaded or deleted
operator new/operator deleteare not supported.○ Compilation: workarounds for buggy or non-standard-compliant compilers were removed and will not be reintroduced.
○ The
optionsclass for customizing docstring generation was removed.○ The NumPy array class (
py::array) was removed in exchange for a more powerful alternative (nb::ndarray<..>) that additionally supports CPU/GPU tensors produced by various frameworks (NumPy, PyTorch, TensorFlow, JAX, etc.). Its API is not compatible with pybind11, however.● Buffer protocol binding (
.def_buffer()) was removed in favor ofnb::ndarray<..>.● Support for evaluating Python files was removed.
Bullet points marked with ● may be reintroduced eventually, but this will need to be done in a careful opt-in manner that does not affect code complexity, binary size, and compilation/runtime performance of basic bindings that don’t depend on these features.