@@ -275,12 +275,19 @@ desired Python type.
275275- :cpp:class: `nb::tensorflow <tensorflow> `: create a ``tensorflow.python.framework.ops.EagerTensor ``.
276276- :cpp:class: `nb::jax <jax> `: create a ``jaxlib.xla_extension.DeviceArray ``.
277277- :cpp:class: `nb::cupy <cupy> `: create a ``cupy.ndarray ``.
278+ - :cpp:class: `nb::memview <memview> `: create a Python ``memoryview ``.
279+ - :cpp:class: `nb::array_api <array_api> `: create an object that supports the
280+ Python buffer protocol (i.e., is accepted as an argument to ``memoryview() ``)
281+ and also has the DLPack attributes ``__dlpack__ `` and ``__dlpack_device__ ``
282+ (i.e., it is accepted as an argument to a framework's ``from_dlpack() ``
283+ function).
278284- No framework annotation. In this case, nanobind will create a raw Python
279285 ``dltensor `` `capsule <https://docs.python.org/3/c-api/capsule.html >`__
280- representing the `DLPack <https://github.com/dmlc/dlpack >`__ metadata.
286+ representing the `DLPack <https://github.com/dmlc/dlpack >`__ metadata of
287+ a ``DLManagedTensor ``.
281288
282289This annotation also affects the auto-generated docstring of the function,
283- which in this case becomes:
290+ which in this example's case becomes:
284291
285292.. code-block :: python
286293
@@ -458,6 +465,21 @@ interpreted as follows:
458465- :cpp:enumerator: `rv_policy::move ` is unsupported and demoted to
459466 :cpp:enumerator: `rv_policy::copy `.
460467
468+ Note that when a copy is returned, the copy is made by the framework, not by
469+ nanobind itself.
470+ For example, ``numpy.array() `` is passed the keyword argument ``copy `` with
471+ value ``True ``, or the PyTorch tensor's ``clone() `` method is immediately
472+ called to create the copy.
473+ This design has a couple of advantages.
474+ First, nanobind does not have a build-time dependency on the libraries and
475+ frameworks (NumPy, PyTorch, CUDA, etc.) that would otherwise be necessary
476+ to perform the copy.
477+ Second, frameworks have the opportunity to optimize how the copy is created.
478+ The copy is owned by the framework, so the framework can choose to use a custom
479+ memory allocator, over-align the data, etc. based on the nd-array's size,
480+ the specific CPU, GPU, or memory types detected, etc.
481+
482+
461483.. _ndarray-temporaries :
462484
463485Returning temporaries
@@ -643,26 +665,80 @@ support inter-framework data exchange, custom array types should implement the
643665- `__dlpack__ <https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.__dlpack__.html#array_api.array.__dlpack_ _>`__ and
644666- `__dlpack_device__ <https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.__dlpack_device__.html#array_api.array.__dlpack_device_ _>`__
645667
646- methods. This is easy thanks to the nd-array integration in nanobind. An example is shown below:
668+ methods.
669+ These, as well as the buffer protocol, are implemented in the object returned
670+ by nanobind when specifying :cpp:class: `nb::array_api <array_api> ` as the
671+ framework template parameter.
672+ For example:
673+
674+ .. code-block :: cpp
675+
676+ class MyArray {
677+ double* d;
678+ public:
679+ MyArray() { d = new double[5] { 0.0, 1.0, 2.0, 3.0, 4.0 }; }
680+ ~MyArray() { delete[] d; }
681+ double* data() const { return d; }
682+ };
683+
684+ nb::class_<MyArray>(m, "MyArray")
685+ .def(nb::init<>())
686+ .def("array_api", [](const MyArray& self) {
687+ return nb::ndarray<nb::array_api, double>(self.data(), {5});
688+ }, nb::rv_policy::reference_internal);
689+
690+ which can be used as follows:
691+
692+ .. code-block :: pycon
693+
694+ >>> import my_extension
695+ >>> ma = my_extension.MyArray()
696+ >>> aa = ma.array_api()
697+ >>> aa.__dlpack_device__()
698+ (1, 0)
699+ >>> import numpy as np
700+ >>> x = np.from_dlpack(aa)
701+ >>> x
702+ array([0., 1., 2., 3., 4.])
703+
704+ The DLPack methods can also be provided for the class itself, by implementing
705+ ``__dlpack__() `` as a wrapper function.
706+ For example, by adding the following lines to the binding:
647707
648708.. code-block :: cpp
649709
650- nb::class_<MyArray>(m, "MyArray")
651- // ...
652- .def("__dlpack__", [](nb::kwargs kwargs) {
653- return nb::ndarray<>( /* ... */);
654- })
655- .def("__dlpack_device__", []() {
656- return std::make_pair(nb::device::cpu::value, 0);
657- });
710+ .def("__dlpack__", [](nb::pointer_and_handle<MyArray> self,
711+ nb::kwargs kwargs) {
712+ using array_api_t = nb::ndarray<nb::array_api, double>;
713+ nb::object aa = nb::cast(array_api_t(self.p->data(), {5}),
714+ nb::rv_policy::reference_internal,
715+ self.h);
716+ return aa.attr("__dlpack__")(**kwargs);
717+ })
718+ .def("__dlpack_device__", [](nb::handle /*self*/) {
719+ return std::make_pair(nb::device::cpu::value, 0);
720+ })
658721
659- Returning a raw :cpp:class: `nb::ndarray <ndarray> ` without framework annotation
660- will produce a DLPack capsule, which is what the interface expects.
722+ the class can be used as follows:
723+
724+ .. code-block :: pycon
725+
726+ >>> import my_extension
727+ >>> ma = my_extension.MyArray()
728+ >>> ma.__dlpack_device__()
729+ (1, 0)
730+ >>> import numpy as np
731+ >>> y = np.from_dlpack(ma)
732+ >>> y
733+ array([0., 1., 2., 3., 4.])
734+
735+
736+ The ``kwargs `` argument in the implementation of ``__dlpack__ `` above can be
737+ used to support additional parameters (e.g., to allow the caller to request a
738+ copy). See
739+ `__dlpack__() <https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.__dlpack__.html >`__
740+ in the Python array API standard for details.
661741
662- The ``kwargs `` argument can be used to provide additional parameters (for
663- example to request a copy), please see the DLPack documentation for details.
664- Note that nanobind does not yet implement the versioned DLPack protocol. The
665- version number should be ignored for now.
666742
667743Frequently asked questions
668744--------------------------
@@ -708,7 +784,3 @@ be more restrictive. Presently supported dtypes include signed/unsigned
708784integers, floating point values, complex numbers, and boolean values. Some
709785:ref: `nonstandard arithmetic types <ndarray-nonstandard >` can be supported as
710786well.
711-
712- Nanobind can receive and return *read-only * arrays via the buffer protocol when
713- exhanging data with NumPy. The DLPack interface currently ignores this
714- annotation.
0 commit comments