GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Introduce `mpl::array` and `mpl::defer` by iccir · Pull Request #32532 · matplotlib/matplotlib

Conversation

@iccir

@iccir iccir commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

PR summary

This pull request adds a new mpl::array C++ class as well as mpl::defer to mplutils.h. It changes the qhull module to use these instead of pybind11's py::array. Additional modules will be ported in the future.

ndarray Overview

nanobind provides an ndarray class for NumPy array access. While similar to pybind11's array class, it is not a drop-in replacement.

  • nb::ndarray does not automatically convert Python sequences.
  • nb::ndarray requires a const qualifier on scalar types in order to accept read-only arrays.
  • nb::ndarray has a different API. mutable_data() is simply data() on a non-const array. There is no unchecked() or unchecked_mutable(), etc.

nb::ndarray also provides shape constraints. In theory, these are great for code readability, as we can indicate an (N, 3) integer array as follows:

using TriangleArray = nb::ndarray<int, nb::shape<-1, 3>>;

Unfortunately, nanobind is exceedingly vague when it fails to match a type signature to a bound function. When porting modules, you often end up with:

TypeError: foo(): incompatible function arguments.
The following argument types are supported:
1. foo(x: ndarray[dtype=uint8, order='C']) -> tuple

Invoked with types: ndarray

The root cause can be your scalar is int instead of const int. Or it can be that the shape constraint is mismatched. You don't know. This is especially fun when your function takes 5+ different ndarray types and nanobind doesn't indicate which one caused the failure.

nanobind also requires that all typecasters be noexcept and return a simple false in the event of an error. Hence, there is no way to provide detailed information on why the type caster failed.

Typical Recommendation

The typical recommendation to the above problem is: "don't use a type caster, declare your function to take an nb::object and then convert inside the function body":

static nb::tuple foo(nb::object src_object)
{
    nb::ndarray<double, nb::device::cpu, nb::c_contig> foo_arr;

    if (nb::try_cast(src_object, rect_arr)) {
        …
    }

    …
}

This is not ideal:

  1. The function's signature is vague and results in vague types with nanobind's stubgen tool.
  2. You fail if src_object is a Python sequence.
  3. nb::try_cast returns a boolean. nb::cast throws a vague error message.

My Solution

My solution involves two parts: a simple wrapper which defers object creation combined with a modified array class which provides detailed error messages.

mpl::defer

mpl::defer is a simple pybind or nanobind wrapper class which defers the creation of an object until value() is called:

template <typename T>
class defer : public py::object // (or nb::object)
{
    … // PYBIND11_OBJECT_DEFAULT or NB_OBJECT_DEFAULT
    template <typename... Args>
    T value(Args &&...args) { return T(*this, args...); }
};

This is used in a bound function as follows:

static nb::tuple foo(const mpl::defer<DeferredObject> &src)
{
    auto actual = src.value();
    …
}

mpl::array

mpl::array is effectively a vendored version of nb::ndarray from nanobind's "ndarray.h". It automatically handles Python sequences and provides detailed error messages when a conversion fails. It also allows for "unmanaged" variants which are not backed by a NumPy array.

It will eventually replace several of our utility functions and classes:

Old New
safe_first_shape() mpl::array::has_members()
check_trailing_shape() mpl::shape / mpl::ndim constraints
class empty (array.h) mpl::array::unmanaged_empty()
class scalar (array.h) mpl::array::unmanaged_scalar()
Manually comparing shape of arrays mpl::array::is_same_shape()

In this PR, it wraps a py::array and lives in "py_array.h". In a future PR, there will be a variant that wraps an nb::ndarray and lives in "mplarray.h". Both variants can exist as we adopt nanobind.

Note

For simplicity, an mpl::array always has an associated data type and shape. We have a few situations that use data-type-less or shapeless arrays. Those situations will continue to directly use py::array or nb::ndarray.

Qhull changes

The changes to qhull are straightforward:

  • Two py::array types change to mpl::array.
  • mutable_data() becomes data().
  • An ndim() check is removed as the mpl::array constructor handles this for us.

C++ Disclosure

The creation of C++ container classes and heavy template metaprogramming is not something that I do everyday. It involves copious amounts of coffee, epic video game battle music, and intense focus.

Please scrutinize accordingly. While most of the heavy lifting is directly ported from nanobind, I am still filling gaps in my C++ knowledge.

AI Disclosure

  • I use AI for web search due to search engines becoming less reliable.
  • A lot of code was adapted from my nanobind experiment where I used AI to help update my C++ knowledge to C++17.
  • I used AI to review my implementation of py_array.h and check it against nanobind's ndarray class.

PR quality check

  • Use an expressive title, e.g. "Fix title font property precedence"
  • New and changed code is tested
  • [/] Plotting related features are demonstrated in an example
  • [/] New features and API changes have release notes
  • [/] Documentation complies with general and docstring guidelines

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@iccir
Introduce mpl::defer and mpl::array. Use in qhull.
c82b28b
@github-actions github-actions Bot added the topic: contour label Oct 23, 2026
@iccir
iccir force-pushed the pybind-mplarray-initial branch from 41da0ea to c82b28b Compare October 23, 2026 15:13
@iccir

iccir commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

I'm going to go ahead and mark this as "Ready for review". I believe that the Windows test failure is due to a timeout in the test and AppVeyor didn't make it to the finish line in time.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@iccir
iccir marked this pull request as ready for review October 23, 2026 16:23
iccir added 2 commits October 23, 2026 13:16
@iccir
Add more methods to mpl::array
5a547d8
@iccir
Merge branch 'main' of https://github.com/matplotlib/matplotlib into …
2d61980
…pybind-mplarray-initial
@iccir iccir mentioned this pull request Oct 24, 2026
Fully adopt mpl::array and mpl::defer #32536
Draft
2 tasks done
@iccir
Remove mpl::array_view, iterator support, new naming convention
a806d36
@iccir

iccir commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

I spent the weekend doing performance benchmarks and have removed mpl::array_view (which was copy-pasted from nanobind::ndarray_view).

  • There are occasions where the compiler spits out 5% faster code for nb::ndarray_view compared to mpl::array or nb::ndarray. Despite identical code, I could never get mpl::array_view to go down this optimized path.
  • If performance is truly that critical, accessing .data() and doing manual pointer math will always result in slightly faster code generation.

I also tweaked the naming convention to make code more readable at the call site:

Type / Call Variable Name
mpl::defer<FooArray> or
std::optional<mpl::defer<…>>
foo_obj
FooArray foo
foo_arr.data() foo_data
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@iccir

iccir commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Regarding my previous comment, I have since figured out how to make the compiler use the optimized path for operator(). There is no need for an array_view class. #32536 will include this optimization.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@iccir iccir mentioned this pull request Oct 30, 2026
nanobind Umbrella #32560
Open
@iccir iccir added this to nanobind Nov 2, 2026
@github-project-automation github-project-automation Bot moved this to Todo in nanobind Nov 2, 2026
@iccir iccir moved this from Todo to Ready for Review in nanobind Nov 2, 2026
This was referenced Nov 5, 2026
Introduce mpl buffer functions to replace def_buffer() #32576
Open
Replace pybind11 type casters with mpl::convert #32581
Draft

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

topic: contour

Projects

nanobind
Status: Ready for Review

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@iccir
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.